This repository has been archived by the owner on Jan 31, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🚀 Merge pull request #21 from tpcofficial/dev
0.2.0 - Working Discord and Google OAuth2 providers
- Loading branch information
Showing
9 changed files
with
485 additions
and
120 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/** | ||
* Discord OAuth2 flow | ||
* | ||
* 1. Obtain an access token from Discord Authorization server | ||
* 2. Examine scopes of access granted by user | ||
* 3. Send the access token to an API | ||
* | ||
*/ | ||
class DiscordHandler { | ||
constructor(configobj,extraOptions = {}) { | ||
if (!configobj) | ||
throw "No configuration object provided" | ||
|
||
this.client_id = configobj.client_id; | ||
this.client_secret = configobj.client_secret; | ||
|
||
this.redirect_uri = configobj.redirect_uri; //We recommend setting this to something like https://example.org/api/oauth2/google | ||
this.scope = configobj.scope >= 1 ? configobj.scope : "email identify";//Default to profile scope if no scope is defined - && configobj.isArray() | ||
this.auth_base_url = "https://discord.com/api/v7/oauth2/authorize" | ||
this.token_url = "https://discord.com/api/v7/oauth2/token" | ||
|
||
this.libs = {}; | ||
this.libs.fetch = require('node-fetch'); | ||
this.libs.log = require('../lib/logging-debug'); | ||
} | ||
|
||
startFlow() {//Should return a uri to begin the OAuth2 flow and gain user consent | ||
this.libs.log.info('Start of OAuth2 flow, generating redirect uri to gain user consent'); | ||
try { | ||
return `${this.auth_base_url}?client_id=${this.client_id}&response_type=code&scope=${this.scope}&redirect_uri=${this.redirect_uri}/callback`; | ||
} catch (e) { | ||
this.libs.log.error("Failed to start OAuth2 flow: Couldn't generate (and/or) return the consent uri"); | ||
throw "Failed to generate consent uri"; | ||
} | ||
} | ||
|
||
async stopFlow(flowResponse) {//Should receive the token, automatically and prepare it for the user - the token is not stored and this should return USER DATA only | ||
if (!flowResponse || !flowResponse.code) | ||
return false | ||
|
||
await this.libs.fetch(`${this.token_url}?code=${flowResponse.code}&client_id=${this.client_id}&client_secret=${this.client_secret}&redirect_uri=${this.redirect_uri}/callback&scope=${this.scope}&grant_type=authorization_code`, {method:'POST'})// Get user code from query data -> ${flowResponse.code} | ||
.this(res => res.json()) | ||
.this(async json => { | ||
if (json.token_type == 'Bearer' && json.access_token) { | ||
await this.libs.fetch(`https://discord.com/api/v7/users/@me`,{method:'POST',headers: {'Authorization': `Bearer ${json.access_token}`}}) | ||
.this(json => {return json}) | ||
} else { | ||
throw "[Discord] No valid authentication data (bearer token) returned by the remote API\n"+JSON.stringify(json); | ||
} | ||
})// Get user token -> function fetch ... | ||
// Get user data (email, name) | ||
} | ||
|
||
renewToken(token) { //Should renew the token | ||
throw "Unimplemented"+token | ||
} | ||
|
||
getData(token) { //Get's user data | ||
throw "Unimplemented"+token | ||
} | ||
} | ||
|
||
module.exports = {DiscordHandler}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,81 @@ | ||
/** | ||
* Discord OAuth2 flow | ||
* | ||
* 1. Obtain an access token from Discord Authorization server | ||
* 2. Examine scopes of access granted by user | ||
* 3. Send the access token to an API | ||
* Discord OAuth2 user data retrieval | ||
* | ||
* 1. Start your consent request | ||
* 2. Forward callback data back to the OAuth2Lib | ||
* 3. Get your data! (async) | ||
*/ | ||
|
||
class DiscordHandler { | ||
constructor(configobj,extraOptions = {}) { | ||
constructor(configobj,extraOptions) { | ||
if (!configobj) | ||
throw "No configuration object provided" | ||
|
||
this.client_id = configobj.client_id; | ||
this.client_secret = configobj.client_secret; | ||
|
||
//Required | ||
this.config = {}; | ||
this.config.client_id = configobj.client_id; | ||
this.config.client_secret = configobj.client_secret; | ||
this.config.redirect_uri = configobj.redirect_uri; //We recommend setting this to something like https://example.org/api/oauth2/Discord | ||
this.config.platform_name = 'Discord'; | ||
|
||
this.redirect_uri = configobj.redirect_uri; //We recommend setting this to something like https://example.org/api/oauth2/google | ||
this.scope = configobj.scope >= 1 ? configobj.scope : "email identify";//Default to profile scope if no scope is defined - && configobj.isArray() | ||
this.auth_base_url = "https://discord.com/api/v7/oauth2/authorize" | ||
this.token_url = "https://oauth2.googleapis.com/token" | ||
//Optional | ||
this.config.response_type = configobj.response_type ? configobj.response_type : 'code'; | ||
this.config.scope = configobj.scope >= 1 ? configobj.scope : "identify email";//Default to profile scope if no scope is defined - && configobj.isArray() | ||
this.config.auth_base_url = configobj.auth_base_url ? configobj.auth_base_url : "https://discord.com/api/v7/oauth2/authorize"; | ||
this.config.token_url = configobj.token_url ? configobj.token_url : "https://discord.com/api/v7/oauth2/token"; | ||
|
||
this.libs = {}; | ||
this.libs.fetch = require('node-fetch'); | ||
this.libs.log = require('../lib/logging-debug'); | ||
this.libs.log.info('Logging loaded, now loading OAuth2Generic') | ||
this.libs.OAuth2Generic = require('./generic').GenericHandler; | ||
this.libs.log.success("Success loaded OAuth2Generic @ ('./generic')"); | ||
this.libs.log.info('Loading OAuth2Lib with config: '+JSON.stringify(this.config)); | ||
this.libs.OAuth2Lib = new this.libs.OAuth2Generic(this.config); | ||
this.libs.log.success('Created OAuth2Lib') | ||
} | ||
|
||
startFlow() {//Should return a uri to begin the OAuth2 flow and gain user consent | ||
this.libs.log.info('Start of OAuth2 flow, generating redirect uri to gain user consent'); | ||
startFlow() { | ||
try { | ||
return `${this.auth_base_url}?client_id=${this.client_id}&response_type=code&scope=${this.scope}&redirect_uri=${this.redirect_uri}/callback`; | ||
const redirecturl = this.libs.OAuth2Lib.startFlow(); | ||
return redirecturl; | ||
} catch (e) { | ||
this.libs.log.error("Failed to start OAuth2 flow: Couldn't generate (and/or) return the consent uri"); | ||
throw "Failed to generate consent uri"; | ||
throw "[Discord] Could not get the ./generic handler to generic a consent redirect url" | ||
} | ||
} | ||
|
||
async stopFlow(flowResponse) {//Should receive the token, automatically and prepare it for the user - the token is not stored and this should return USER DATA only | ||
if (!flowResponse || !flowResponse.code) | ||
return false | ||
|
||
await this.libs.fetch(`${this.token_url}?code=${flowResponse.code}&client_id=${this.client_id}&client_secret=${this.client_secret}&redirect_uri=${this.redirect_uri}/callback&scope=${this.scope}&grant_type=authorization_code`, {method:'POST'})// Get user code from query data -> ${flowResponse.code} | ||
.this(res => res.json()) | ||
.this(async json => { | ||
if (json.token_type == 'Bearer' && json.access_token) { | ||
await this.libs.fetch(`https://discord.com/api/v7/users/@me`,{method:'POST',headers: {'Authorization': `Bearer ${json.access_token}`}}) | ||
.this(json => {return json}) | ||
} else { | ||
throw "[Discord] No valid authentication data (bearer token) returned by the remote API\n"+JSON.stringify(json); | ||
} | ||
})// Get user token -> function fetch ... | ||
// Get user data (email, name) | ||
} | ||
|
||
renewToken(token) {//Should renew the token | ||
|
||
} | ||
|
||
getData(token) {//Get's user data | ||
|
||
async stopFlow(returnedData) { | ||
return new Promise ( async (resolve, reject) => { | ||
if (returnedData.code) { | ||
returnedData['bodypost']=true; | ||
this.libs.OAuth2Lib.stopFlow(returnedData) | ||
.then(tokenData => { | ||
this.libs.log.info('[Discord] attempting to get user data'); | ||
this.libs.fetch(`https://discord.com/api/v7/users/@me`,{method:'GET',headers: {'Authorization': `Bearer ${tokenData.access_token}`}}) | ||
.then(this.libs.checkStatus) | ||
.then(res => res.json()) | ||
.then(json => { | ||
this.libs.log.success('got the user data!'); | ||
this.libs.log.info(JSON.stringify(json)) | ||
resolve( { | ||
platformid:json.id, | ||
email:json.email, | ||
name:json.name, | ||
picture:`https://cdn.discordapp.com/avatars/${json.id}/${json.avatar}.png`, // https://github.com/wohali/oauth2-discord-new/issues/14 | ||
given_name:json.given_name, | ||
locale:json.locale | ||
}) | ||
}) | ||
.catch(()=>{ | ||
reject("[Discord] API could not complete the OAuth2 token exchange") | ||
}) | ||
}) | ||
.catch(e => {reject("[Discord] User did not return with a valid auth code (during stopFlow)\n"+String(e))}) | ||
} else { | ||
reject("[Discord] User did not return with an auth code at all") | ||
} | ||
}); | ||
} | ||
} | ||
|
||
module.exports = {DiscordHandler}; | ||
module.exports = {DiscordHandler} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,110 @@ | ||
/** | ||
* Generic OAuth2 flow | ||
* | ||
* 1. Obtain an access token from Google Authorization server | ||
* 2. Examine scopes of access granted by user | ||
* 3. Send the access token to an API | ||
* | ||
*/ | ||
class GenericHandler { | ||
|
||
// { | ||
// "client_id": '', | ||
// "client_secret": '', | ||
// "redirect_uri": '', | ||
// "scopes":"", | ||
constructor(configobj,extraOptions = {}) { | ||
if (!configobj) | ||
throw "No configuration object provided" | ||
|
||
//Required | ||
this.client_id = configobj.client_id; | ||
this.client_secret = configobj.client_secret; | ||
this.redirect_uri = configobj.redirect_uri; //We recommend setting this to something like https://example.org/api/oauth2/google | ||
this.auth_base_url = configobj.auth_base_url | ||
this.token_url = configobj.token_url | ||
|
||
// "authentication_uri":'', | ||
// "platform_name":'', | ||
// "token_uri":'', | ||
// "":"" | ||
// } | ||
//Optional | ||
this.response_type = configobj.response_type ? configobj.response_type : null; | ||
this.scope = configobj.scope ? configobj.scope : null;//Default to profile scope if no scope is defined - && configobj.isArray() | ||
this.platform_name = configobj.platform_name ? configobj.platform_name : 'generic'; | ||
|
||
// // Query for access code | ||
this.libs = {}; | ||
this.libs.fetch = require('node-fetch'); | ||
this.libs.log = require('../lib/logging-debug'); | ||
this.libs.checkStatus = (res) => {if (res.ok) {return res} else { console.log(res); throw "Unhealthy response"}}; | ||
} | ||
|
||
// // Query for access token | ||
startFlow() {//Should return a uri to begin the OAuth2 flow and gain user consent | ||
this.libs.log.info(`[${this.platform_name}] Start of OAuth2 flow, generating redirect uri to gain user consent`); | ||
try { | ||
return `${this.auth_base_url}?client_id=${this.client_id}&response_type=${this.response_type}&scope=${this.scope}&redirect_uri=${this.redirect_uri}/callback`; | ||
} catch (e) { | ||
this.libs.log.error(`[${this.platform_name}] Failed to start OAuth2 flow: Couldn't generate (and/or) return the consent uri`); | ||
throw `[${this.platform_name}] Failed to generate consent uri\n${e}`; | ||
} | ||
} | ||
|
||
// // Query for data (may differ across platforms) | ||
async stopFlow(flowResponse) {//Should receive the token, automatically and prepare it for the user - the token is not stored and this should return USER DATA only | ||
return new Promise ( (resolve, reject) => { | ||
if (flowResponse.code) { | ||
this.libs.log.info(`[${this.platform_name}] Code spotted, exchanging`); | ||
//console.log(`${this.token_url}?code=${flowResponse.code}&client_id=${this.client_id}&client_secret=${this.client_secret}&redirect_uri=${this.redirect_uri}/callback&scope=${this.scope}&grant_type=authorization_code`) | ||
if (!flowResponse.bodypost) { | ||
this.libs.log.info(`[${this.platform_name}] Query method POST exchange`) | ||
this.libs.fetch(`${this.token_url}?code=${flowResponse.code}&client_id=${this.client_id}&client_secret=${this.client_secret}&redirect_uri=${this.redirect_uri}/callback&scope=${this.scope}&grant_type=authorization_code`, {method:'POST'})// Get user code from query data -> ${flowResponse.code} | ||
.then(this.libs.checkStatus) | ||
.then(res => res.json()) | ||
.then(json => { | ||
//this.libs.fetch(``,{method:`GET`,headers:{'Authorization':`Bearer ${res.json.access_token}`}}) | ||
if (json.access_token) { | ||
this.libs.log.success(`[${this.platform_name}] code exchange was successful`); | ||
resolve(json); | ||
} else { | ||
this.libs.log.error(`[${this.platform_name}] code exchange failed`); | ||
reject(`[${this.platform_name}] failed to complete the code excahnge`); | ||
} | ||
}) | ||
.catch(e => { | ||
this.libs.log.warn(`[${this.platform_name}] failed to give us a valid access_token`); | ||
reject(`[${this.platform_name}] failed to run through the code exchange flow\n+${String(e)}`); | ||
}) | ||
} else { | ||
const {URLSearchParams} = require('url'); | ||
const params = new URLSearchParams({ | ||
code:flowResponse.code, | ||
client_id:this.client_id, | ||
client_secret:this.client_secret, | ||
redirect_uri:this.redirect_uri+'/callback', | ||
scope:this.scope, | ||
grant_type:'authorization_code' | ||
}); | ||
this.libs.log.info(`[${this.platform_name}] Form body method POST exchange ${flowResponse.bodypost}`) | ||
this.libs.log.info(`${this.token_url} | ${params.toString()}`) | ||
this.libs.fetch(`${this.token_url}`, {method:'POST',body: params})// Get user code from query data -> ${flowResponse.code} | ||
.then(this.libs.checkStatus) | ||
.then(res => res.json()) | ||
.then(json => { | ||
//this.libs.fetch(``,{method:`GET`,headers:{'Authorization':`Bearer ${res.json.access_token}`}}) | ||
if (json.access_token) { | ||
this.libs.log.success(`[${this.platform_name}] code exchange was successful`); | ||
resolve(json); | ||
} else { | ||
this.libs.log.warn(`[${this.platform_name}] code exchange failed`); | ||
reject(`[${this.platform_name}] failed to complete the code excahnge`); | ||
} | ||
}) | ||
.catch(e => { | ||
this.libs.log.error(`[${this.platform_name}] failed to give us a valid access_token`); | ||
reject(`[${this.platform_name}] failed to run through the code exchange flow\n+${String(e)}`); | ||
}) | ||
} | ||
} else { | ||
this.libs.log.error('Google did not give us valid data\n'+JSON.stringify(flowResponse)); | ||
reject(`[${this.platform_name}] API did not respond with a valid authentication code or token`); | ||
} | ||
}) | ||
// Get user data (email, name) | ||
} | ||
|
||
renewToken(renew_token) { //Should renew the token | ||
throw "Unimplemented"+token | ||
} | ||
} | ||
|
||
module.exports = {GenericHandler}; |
Oops, something went wrong.