Skip to content

Commit

Permalink
style(service): users/auth: wip: oauth2 typescript: more cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
restjohn committed Sep 10, 2024
1 parent 9a3b0ab commit 333bcb5
Showing 1 changed file with 107 additions and 94 deletions.
201 changes: 107 additions & 94 deletions service/src/ingress/ingress.protocol.oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,42 @@
import { InternalOAuthError, Strategy as OAuth2Strategy, StrategyOptions as OAuth2Options, VerifyFunction } from 'passport-oauth2'
import { TokenAssertion, JWTService } from './verification'
import base64 from 'base-64'
import { IdentityProvider } from './entities.authentication'
import { Authenticator } from 'passport'
const api = require('../api')
const log = require('../logger')
const User = require('../models/user')
const Role = require('../models/role')
const { app, passport, tokenService } = require('./index');

interface MageOAuth2Options extends OAuth2Options {
profileURL: string
export type OAuth2ProtocolSettings =
Pick<OAuth2Options,
| 'clientID'
| 'clientSecret'
| 'authorizationURL'
| 'tokenURL'
| 'scope'
| 'pkce'
> &
{
profileURL: string,
headers?: { basic?: boolean | null | undefined },
profile: OAuth2ProfileKeys
}
export type OAuth2ProfileKeys = {
id: string
email: string
displayName: string
}

class OAuth2ProfileStrategy extends OAuth2Strategy {

private _profileURL: string

constructor(options: MageOAuth2Options, verify: VerifyFunction) {
constructor(options: OAuth2Options, readonly profileURL: string, verify: VerifyFunction) {
super(options as OAuth2Options, verify)
if (!options.profileURL) {
throw new TypeError('OAuth2: missing profileURL')
}
this._profileURL = options.profileURL
this._oauth2.useAuthorizationHeaderforGET(true)
}

userProfile(accessToken: string, done: (err: unknown, profile?: any) => void): void {
this._oauth2.get(this._profileURL, accessToken, (err, body) => {
this._oauth2.get(this.profileURL, accessToken, (err, body) => {
if (err) {
return done(new InternalOAuthError('error fetching oauth2 user profile', err))
}
Expand All @@ -48,95 +59,97 @@ class OAuth2ProfileStrategy extends OAuth2Strategy {
}
}

function configure(strategy) {
function configure(strategy: IdentityProvider, passport: Authenticator, ) {
log.info(`configuring ${strategy.title} oauth2 authentication`);
const customHeaders = strategy.settings.headers?.basic ? {
authorization: `Basic ${base64.encode(`${strategy.settings.clientID}:${strategy.settings.clientSecret}`)}`
const settings = strategy.protocolSettings as OAuth2ProtocolSettings
const customHeaders = settings.headers?.basic ? {
authorization: `Basic ${base64.encode(`${settings.clientID}:${settings.clientSecret}`)}`
} : undefined
passport.use(strategy.name, new OAuth2ProfileStrategy(
{
clientID: strategy.settings.clientID,
clientSecret: strategy.settings.clientSecret,
callbackURL: `/auth/${strategy.name}/callback`,
authorizationURL: strategy.settings.authorizationURL,
tokenURL: strategy.settings.tokenURL,
profileURL: strategy.settings.profileURL,
customHeaders: customHeaders,
scope: strategy.settings.scope,
pkce: strategy.settings.pkce,
/**
* cast to `any` because `@types/passport-oauth2` incorrectly does not allow `boolean` for the `store` entry
* https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js#L107
*/
store: true as any
},
function (accessToken, refreshToken, profileResponse, done) {
const profile = profileResponse.json;

if (!profile[strategy.settings.profile.id]) {
log.warn("JSON: " + JSON.stringify(profile) + " RAW: " + profileResponse.raw);
return done(`OAuth2 user profile does not contain id property named ${strategy.settings.profile.id}`);
}
const strategyOptions: OAuth2Options = {
clientID: settings.clientID,
clientSecret: settings.clientSecret,
callbackURL: `/auth/${strategy.name}/callback`,
authorizationURL: settings.authorizationURL,
tokenURL: settings.tokenURL,
customHeaders: customHeaders,
scope: settings.scope,
pkce: settings.pkce,
/**
* cast to `any` because `@types/passport-oauth2` incorrectly does not allow `boolean` for the `store` entry
* https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js#L107
*/
store: true as any
}
const verify: VerifyFunction = (accessToken, refreshToken, profileResponse, done) => {
const profile = profileResponse.json
const profileKeys = settings.profile
if (!profile[profileKeys.id]) {
log.warn("JSON: " + JSON.stringify(profile) + " RAW: " + profileResponse.raw);
return done(`OAuth2 user profile does not contain id property named ${profileKeys.id}`);
}

const profileId = profile[strategy.settings.profile.id];

// TODO: users-next
// TODO: should be by strategy name, not strategy type
User.getUserByAuthenticationStrategy(strategy.type, profileId, function (err, user) {
if (err) return done(err);

if (!user) {
// Create an account for the user
Role.getRole('USER_ROLE', function (err, role) {
if (err) return done(err);

let email = null;
if (profile[strategy.settings.profile.email]) {
if (Array.isArray(profile[strategy.settings.profile.email])) {
email = profile[strategy.settings.profile.email].find(email => {
email.verified === true
});
} else {
email = profile[strategy.settings.profile.email];
}
const profileId = profile[settings.profile.id];

// TODO: users-next
// TODO: should be by strategy name, not strategy type
User.getUserByAuthenticationStrategy(strategy.type, profileId, function (err, user) {
if (err) return done(err);

if (!user) {
// Create an account for the user
Role.getRole('USER_ROLE', function (err, role) {
if (err) {
return done(err)
}
const profileEmail = profile[profileKeys.email]
if (profile[profileKeys.email]) {
if (Array.isArray(profile[profileKeys.email])) {
email = profile[profileKeys.email].find(email => {
email.verified === true
});
} else {
log.warn(`OAuth2 user profile does not contain email property named ${strategy.settings.profile.email}`);
log.debug(JSON.stringify(profile));
email = profile[settings.profile.email];
}

const user = {
username: profileId,
displayName: profile[strategy.settings.profile.displayName] || profileId,
email: email,
active: false,
roleId: role._id,
authentication: {
type: strategy.type,
id: profileId,
authenticationConfiguration: {
name: strategy.name
}
}
};
// TODO: users-next
new api.User().create(user).then(newUser => {
if (!newUser.authentication.authenticationConfiguration.enabled) {
log.warn(newUser.authentication.authenticationConfiguration.title + " authentication is not enabled");
return done(null, false, { message: 'Authentication method is not enabled, please contact a MAGE administrator for assistance.' });
} else {
log.warn(`OAuth2 user profile does not contain email property named ${profileKeys.email}`);
log.debug(JSON.stringify(profile));
}

const user = {
username: profileId,
displayName: profile[profileKeys.displayName] || profileId,
email: email,
active: false,
roleId: role._id,
authentication: {
type: strategy.type,
id: profileId,
authenticationConfiguration: {
name: strategy.name
}
return done(null, newUser);
}).catch(err => done(err));
});
} else if (!user.active) {
return done(null, user, { message: "User is not approved, please contact your MAGE administrator to approve your account." });
} else if (!user.authentication.authenticationConfiguration.enabled) {
log.warn(user.authentication.authenticationConfiguration.title + " authentication is not enabled");
return done(null, user, { message: 'Authentication method is not enabled, please contact a MAGE administrator for assistance.' });
} else {
return done(null, user);
}
});
}));
}
};
// TODO: users-next
new api.User().create(user).then(newUser => {
if (!newUser.authentication.authenticationConfiguration.enabled) {
log.warn(newUser.authentication.authenticationConfiguration.title + " authentication is not enabled");
return done(null, false, { message: 'Authentication method is not enabled, please contact a MAGE administrator for assistance.' });
}
return done(null, newUser);
}).catch(err => done(err));
});
} else if (!user.active) {
return done(null, user, { message: "User is not approved, please contact your MAGE administrator to approve your account." });
} else if (!user.authentication.authenticationConfiguration.enabled) {
log.warn(user.authentication.authenticationConfiguration.title + " authentication is not enabled");
return done(null, user, { message: 'Authentication method is not enabled, please contact a MAGE administrator for assistance.' });
} else {
return done(null, user);
}
});
}
const oauth2Strategy = new OAuth2ProfileStrategy(strategyOptions, verify)

}

function setDefaults(strategy) {
Expand Down

0 comments on commit 333bcb5

Please sign in to comment.