@@ -16,21 +16,23 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.6",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/passport": "^1.0.3",
|
||||
"@types/passport-github2": "^1.2.4",
|
||||
"@types/passport-google-oauth20": "^2.0.3",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
"fs-extra": "^9.0.0",
|
||||
"helmet": "^3.22.0",
|
||||
"morgan": "^1.10.0",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0",
|
||||
"passport": "^0.4.1",
|
||||
"passport-github2": "^0.1.12",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"@types/passport": "^1.0.3",
|
||||
"@types/passport-google-oauth20": "^2.0.3",
|
||||
"@types/cookie-parser": "^1.4.2"
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.6",
|
||||
|
||||
@@ -124,9 +124,15 @@ export const ensuresXRequestedWith = (req: express.Request) => {
|
||||
export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
private readonly provider: string;
|
||||
private readonly providerHandlers: OAuthProviderHandlers;
|
||||
constructor(providerHandlers: OAuthProviderHandlers, provider: string) {
|
||||
private readonly disableRefresh: boolean;
|
||||
constructor(
|
||||
providerHandlers: OAuthProviderHandlers,
|
||||
provider: string,
|
||||
disableRefresh?: boolean,
|
||||
) {
|
||||
this.provider = provider;
|
||||
this.providerHandlers = providerHandlers;
|
||||
this.disableRefresh = disableRefresh ?? false;
|
||||
}
|
||||
|
||||
async start(req: express.Request, res: express.Response): Promise<any> {
|
||||
@@ -164,14 +170,16 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
|
||||
const { user, info } = await this.providerHandlers.handler(req);
|
||||
|
||||
// throw error if missing refresh token
|
||||
const { refreshToken } = info;
|
||||
if (!refreshToken) {
|
||||
throw new Error('Missing refresh token');
|
||||
}
|
||||
if (!this.disableRefresh) {
|
||||
// throw error if missing refresh token
|
||||
const { refreshToken } = info;
|
||||
if (!refreshToken) {
|
||||
throw new Error('Missing refresh token');
|
||||
}
|
||||
|
||||
// set new refresh token
|
||||
setRefreshTokenCookie(res, this.provider, refreshToken);
|
||||
// set new refresh token
|
||||
setRefreshTokenCookie(res, this.provider, refreshToken);
|
||||
}
|
||||
|
||||
// post message back to popup if successful
|
||||
return postMessageResponse(res, {
|
||||
@@ -195,8 +203,10 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
return res.status(401).send('Invalid X-Requested-With header');
|
||||
}
|
||||
|
||||
// remove refresh token cookie before logout
|
||||
removeRefreshTokenCookie(res, this.provider);
|
||||
if (!this.disableRefresh) {
|
||||
// remove refresh token cookie before logout
|
||||
removeRefreshTokenCookie(res, this.provider);
|
||||
}
|
||||
return res.send('logout!');
|
||||
}
|
||||
|
||||
@@ -205,6 +215,12 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
return res.status(401).send('Invalid X-Requested-With header');
|
||||
}
|
||||
|
||||
if (!this.providerHandlers.refresh || this.disableRefresh) {
|
||||
return res.send(
|
||||
`Refresh token not supported for provider: ${this.provider}`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const refreshToken = req.cookies[`${this.provider}-refresh-token`];
|
||||
|
||||
|
||||
@@ -23,4 +23,13 @@ export const providers = [
|
||||
callbackURL: 'http://localhost:7000/auth/google/handler/frame',
|
||||
},
|
||||
},
|
||||
{
|
||||
provider: 'github',
|
||||
options: {
|
||||
clientID: process.env.AUTH_GITHUB_CLIENT_ID!,
|
||||
clientSecret: process.env.AUTH_GITHUB_CLIENT_SECRET!,
|
||||
callbackURL: 'http://localhost:7000/auth/github/handler/frame',
|
||||
},
|
||||
disableRefresh: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -14,21 +14,37 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthProviderFactories, AuthProviderFactory } from './types';
|
||||
import {
|
||||
AuthProviderFactories,
|
||||
AuthProviderRouteHandlers,
|
||||
AuthProviderConfig,
|
||||
} from './types';
|
||||
import { GoogleAuthProvider } from './google';
|
||||
import { GithubAuthProvider } from './github';
|
||||
import { OAuthProvider } from './OAuthProvider';
|
||||
|
||||
export class ProviderFactories {
|
||||
private static readonly providerFactories: AuthProviderFactories = {
|
||||
google: GoogleAuthProvider,
|
||||
github: GithubAuthProvider,
|
||||
};
|
||||
|
||||
public static getProviderFactory(providerId: string): AuthProviderFactory {
|
||||
public static getProviderFactory(
|
||||
config: AuthProviderConfig,
|
||||
): AuthProviderRouteHandlers {
|
||||
const providerId = config.provider;
|
||||
const ProviderImpl = ProviderFactories.providerFactories[providerId];
|
||||
if (!ProviderImpl) {
|
||||
throw Error(
|
||||
`Provider Implementation missing for : ${providerId} auth provider`,
|
||||
);
|
||||
}
|
||||
return ProviderImpl;
|
||||
const providerInstance = new ProviderImpl(config);
|
||||
const oauthProvider = new OAuthProvider(
|
||||
providerInstance,
|
||||
providerId,
|
||||
config.disableRefresh,
|
||||
);
|
||||
return oauthProvider;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { GithubAuthProvider } from './provider';
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import { Strategy as GithubStrategy } from 'passport-github2';
|
||||
import {
|
||||
executeFrameHandlerStrategy,
|
||||
executeRedirectStrategy,
|
||||
} from '../PassportStrategyHelper';
|
||||
import {
|
||||
OAuthProviderHandlers,
|
||||
AuthProviderConfig,
|
||||
RedirectInfo,
|
||||
AuthInfoBase,
|
||||
AuthInfoPrivate,
|
||||
} from '../types';
|
||||
|
||||
export class GithubAuthProvider implements OAuthProviderHandlers {
|
||||
private readonly providerConfig: AuthProviderConfig;
|
||||
private readonly _strategy: GithubStrategy;
|
||||
|
||||
constructor(providerConfig: AuthProviderConfig) {
|
||||
this.providerConfig = providerConfig;
|
||||
this._strategy = new GithubStrategy(
|
||||
{ ...this.providerConfig.options },
|
||||
(accessToken: any, _: any, params: any, profile: any, done: any) => {
|
||||
done(undefined, {
|
||||
profile,
|
||||
accessToken,
|
||||
scope: params.scope,
|
||||
expiresInSeconds: params.expires_in,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async start(req: express.Request, options: any): Promise<RedirectInfo> {
|
||||
return await executeRedirectStrategy(req, this._strategy, options);
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ user: AuthInfoBase; info: AuthInfoPrivate }> {
|
||||
return await executeFrameHandlerStrategy(req, this._strategy);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
import Router from 'express-promise-router';
|
||||
import { AuthProviderRouteHandlers, AuthProviderConfig } from './types';
|
||||
import { ProviderFactories } from './factories';
|
||||
import { OAuthProvider } from './OAuthProvider';
|
||||
|
||||
export const defaultRouter = (provider: AuthProviderRouteHandlers) => {
|
||||
const router = Router();
|
||||
@@ -32,10 +31,7 @@ export const defaultRouter = (provider: AuthProviderRouteHandlers) => {
|
||||
|
||||
export const makeProvider = (config: AuthProviderConfig) => {
|
||||
const providerId = config.provider;
|
||||
const ProviderImpl = ProviderFactories.getProviderFactory(providerId);
|
||||
const providerInstance = new ProviderImpl(config);
|
||||
|
||||
const oauthProvider = new OAuthProvider(providerInstance, providerId);
|
||||
const oauthProvider = ProviderFactories.getProviderFactory(config);
|
||||
const providerRouter = defaultRouter(oauthProvider);
|
||||
return { providerId, providerRouter };
|
||||
};
|
||||
|
||||
@@ -20,12 +20,13 @@ import passport from 'passport';
|
||||
export type AuthProviderConfig = {
|
||||
provider: string;
|
||||
options: any;
|
||||
disableRefresh?: boolean;
|
||||
};
|
||||
|
||||
export interface OAuthProviderHandlers {
|
||||
start(req: express.Request, options: any): Promise<any>;
|
||||
handler(req: express.Request): Promise<any>;
|
||||
refresh(refreshToken: string, scope: string): Promise<any>;
|
||||
refresh?(refreshToken: string, scope: string): Promise<any>;
|
||||
logout?(): Promise<any>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user