Skip to content
This repository has been archived by the owner on Jan 31, 2024. It is now read-only.

Commit

Permalink
🚀 Merge pull request #21 from tpcofficial/dev
Browse files Browse the repository at this point in the history
0.2.0 - Working Discord and Google OAuth2 providers
  • Loading branch information
dudeisbrendan03 authored Jan 12, 2021
2 parents 246022f + faf3a1d commit 8acfc2c
Show file tree
Hide file tree
Showing 9 changed files with 485 additions and 120 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tpcofficial/big-oauth2",
"version": "0.1.52",
"version": "0.2.0",
"description": "A OAuth2 module to easily allow you to integrate your applications authentication with 3rd party IdPs. Returns user data with minimal code to help you create and manage users in your databases!",
"main": "index.js",
"scripts": {
Expand Down
63 changes: 63 additions & 0 deletions providers/discord-classic.js.disabled
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};
102 changes: 60 additions & 42 deletions providers/discord.js
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}
118 changes: 105 additions & 13 deletions providers/generic.js
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};
Loading

0 comments on commit 8acfc2c

Please sign in to comment.