diff --git a/lib/models/user.ts b/lib/models/user.ts index 50e906724..69d08f19e 100644 --- a/lib/models/user.ts +++ b/lib/models/user.ts @@ -27,6 +27,7 @@ export enum ProviderEnum { dropbox = 'dropbox', google = 'google', ldap = 'ldap', + oauth2 = 'oauth2', saml = 'saml', } diff --git a/lib/web/auth/index.ts b/lib/web/auth/index.ts index ed804f652..f79e99eda 100644 --- a/lib/web/auth/index.ts +++ b/lib/web/auth/index.ts @@ -11,9 +11,9 @@ import { DropboxMiddleware } from './dropbox' import { GoogleMiddleware } from './google' import { LdapMiddleware } from './ldap' import { SamlMiddleware } from './saml' -import oauth2 from './oauth2' +import { OAuth2Middleware } from './oauth2' import { EmailMiddleware } from './email' -import {OPenIDMiddleware } from './openid' +import { OPenIDMiddleware } from './openid' const AuthRouter = Router() @@ -51,7 +51,7 @@ if (config.isDropboxEnable) AuthRouter.use(DropboxMiddleware.getMiddleware()) if (config.isGoogleEnable) AuthRouter.use(GoogleMiddleware.getMiddleware()) if (config.isLDAPEnable) AuthRouter.use(LdapMiddleware.getMiddleware()) if (config.isSAMLEnable) AuthRouter.use(SamlMiddleware.getMiddleware()) -if (config.isOAuth2Enable) AuthRouter.use(oauth2) +if (config.isOAuth2Enable) AuthRouter.use(OAuth2Middleware.getMiddleware()) if (config.isEmailEnable) AuthRouter.use(EmailMiddleware.getMiddleware()) if (config.isOpenIDEnable) AuthRouter.use(OPenIDMiddleware.getMiddleware()) diff --git a/lib/web/auth/oauth2/index.js b/lib/web/auth/oauth2/index.js index 1865ad54c..e69de29bb 100644 --- a/lib/web/auth/oauth2/index.js +++ b/lib/web/auth/oauth2/index.js @@ -1,106 +0,0 @@ -'use strict' - -const Router = require('express').Router -const passport = require('passport') -const { Strategy, InternalOAuthError } = require('passport-oauth2') -const config = require('../../../config') -const { passportGeneralCallback } = require('../utils') - -let oauth2Auth = module.exports = Router() - -class OAuth2CustomStrategy extends Strategy { - constructor (options, verify) { - options.customHeaders = options.customHeaders || {} - super(options, verify) - this.name = 'oauth2' - this._userProfileURL = options.userProfileURL - this._oauth2.useAuthorizationHeaderforGET(true) - } - - userProfile (accessToken, done) { - this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) { - var json - - if (err) { - return done(new InternalOAuthError('Failed to fetch user profile', err)) - } - - try { - json = JSON.parse(body) - } catch (ex) { - return done(new Error('Failed to parse user profile')) - } - - let profile = parseProfile(json) - profile.provider = 'oauth2' - - done(null, profile) - }) - } -} - -function extractProfileAttribute (data, path) { - // can handle stuff like `attrs[0].name` - path = path.split('.') - for (const segment of path) { - const m = segment.match(/([\d\w]+)\[(.*)\]/) - data = m ? data[m[1]][m[2]] : data[segment] - } - return data -} - -function parseProfile (data) { - const username = extractProfileAttribute(data, config.oauth2.userProfileUsernameAttr) - const displayName = extractProfileAttribute(data, config.oauth2.userProfileDisplayNameAttr) - const email = extractProfileAttribute(data, config.oauth2.userProfileEmailAttr) - - return { - id: username, - username: username, - displayName: displayName, - email: email - } -} - -OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) { - this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) { - var json - - if (err) { - return done(new InternalOAuthError('Failed to fetch user profile', err)) - } - - try { - json = JSON.parse(body) - } catch (ex) { - return done(new Error('Failed to parse user profile')) - } - - let profile = parseProfile(json) - profile.provider = 'oauth2' - - done(null, profile) - }) -} - -passport.use(new OAuth2CustomStrategy({ - authorizationURL: config.oauth2.authorizationURL, - tokenURL: config.oauth2.tokenURL, - clientID: config.oauth2.clientID, - clientSecret: config.oauth2.clientSecret, - callbackURL: config.serverURL + '/auth/oauth2/callback', - userProfileURL: config.oauth2.userProfileURL, - scope: config.oauth2.scope -}, passportGeneralCallback)) - -oauth2Auth.get('/auth/oauth2', function (req, res, next) { - passport.authenticate('oauth2')(req, res, next) -}) - -// github auth callback -oauth2Auth.get('/auth/oauth2/callback', - passport.authenticate('oauth2', { - successReturnToOrRedirect: config.serverURL + '/', - failureRedirect: config.serverURL + '/' - }) -) diff --git a/lib/web/auth/oauth2/index.ts b/lib/web/auth/oauth2/index.ts new file mode 100644 index 000000000..61ed6af4b --- /dev/null +++ b/lib/web/auth/oauth2/index.ts @@ -0,0 +1,35 @@ +import { Router } from 'express' +import passport from 'passport' + +import { OAuth2CustomStrategy } from './oauth2-custom-strategy' +import { config } from '../../../config' +import { passportGeneralCallback } from '../utils' +import { AuthMiddleware } from '../interface' + +export const OAuth2Middleware: AuthMiddleware = { + getMiddleware (): Router { + const OAuth2Auth = Router() + + passport.use(new OAuth2CustomStrategy({ + authorizationURL: config.oauth2.authorizationURL, + tokenURL: config.oauth2.tokenURL, + clientID: config.oauth2.clientID, + clientSecret: config.oauth2.clientSecret, + callbackURL: config.serverURL + '/auth/oauth2/callback', + userProfileURL: config.oauth2.userProfileURL, + scope: config.oauth2.scope + }, passportGeneralCallback)) + + OAuth2Auth.get('/auth/oauth2', passport.authenticate('oauth2')) + + // github auth callback + OAuth2Auth.get('/auth/oauth2/callback', + passport.authenticate('oauth2', { + successReturnToOrRedirect: config.serverURL + '/', + failureRedirect: config.serverURL + '/' + }) + ) + + return OAuth2Auth + } +} diff --git a/lib/web/auth/oauth2/oauth2-custom-strategy.ts b/lib/web/auth/oauth2/oauth2-custom-strategy.ts new file mode 100644 index 000000000..4fec1cbc8 --- /dev/null +++ b/lib/web/auth/oauth2/oauth2-custom-strategy.ts @@ -0,0 +1,64 @@ +import { InternalOAuthError, Strategy as OAuth2Strategy } from 'passport-oauth2' +import { config } from '../../../config' +import { Profile, ProviderEnum } from '../../../models/user' + +function extractProfileAttribute (data, path: string): any { + // can handle stuff like `attrs[0].name` + const pathArray = path.split('.') + for (const segment of pathArray) { + const regex = /([\d\w]+)\[(.*)\]/ + const m = regex.exec(segment) + data = m ? data[m[1]][m[2]] : data[segment] + } + return data +} + +function parseProfile (data): Partial { + const username = extractProfileAttribute(data, config.oauth2.userProfileUsernameAttr) + const displayName = extractProfileAttribute(data, config.oauth2.userProfileDisplayNameAttr) + const email = extractProfileAttribute(data, config.oauth2.userProfileEmailAttr) + + return { + id: username, + username: username, + displayName: displayName, + emails: [email] + } +} + +class OAuth2CustomStrategy extends OAuth2Strategy { + private readonly _userProfileURL: string; + + constructor (options, verify) { + options.customHeaders = options.customHeaders || {} + super(options, verify) + this.name = 'oauth2' + this._userProfileURL = options.userProfileURL + this._oauth2.useAuthorizationHeaderforGET(true) + } + + userProfile (accessToken, done): void { + this._oauth2.get(this._userProfileURL, accessToken, function (err, body, _) { + let json + + if (err) { + return done(new InternalOAuthError('Failed to fetch user profile', err)) + } + + try { + if (body !== undefined) { + json = JSON.parse(body.toString()) + } + } catch (ex) { + return done(new Error('Failed to parse user profile')) + } + + const profile = parseProfile(json) + profile.provider = ProviderEnum.oauth2 + + done(null, profile) + }) + } +} + +export { OAuth2CustomStrategy }