Merge pull request #1140 from spotify/auth-frontend-state

Auth frontend state
This commit is contained in:
Patrik Oldsberg
2020-06-05 11:48:16 +02:00
committed by GitHub
13 changed files with 451 additions and 31 deletions
+3 -1
View File
@@ -34,7 +34,9 @@
"passport-google-oauth20": "^2.0.0",
"passport-saml": "^1.3.3",
"winston": "^3.2.1",
"yn": "^4.0.0"
"yn": "^4.0.0",
"jwt-decode": "2.2.0",
"@types/jwt-decode": "2.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.6",
@@ -143,6 +143,14 @@ describe('PassportStrategyHelper', () => {
class MyCustomRefreshTokenSuccess extends passport.Strategy {
// @ts-ignore
private _oauth2 = new MyCustomOAuth2Success();
userProfile(_accessToken: string, callback: Function) {
callback(null, {
provider: 'a',
email: 'b',
name: 'c',
picture: 'd',
});
}
}
const mockStrategy = new MyCustomRefreshTokenSuccess();
@@ -16,7 +16,43 @@
import express from 'express';
import passport from 'passport';
import { RedirectInfo, RefreshTokenResponse } from './types';
import jwtDecoder from 'jwt-decode';
import { RedirectInfo, RefreshTokenResponse, ProfileInfo } from './types';
export const makeProfileInfo = (
profile: passport.Profile,
params: any,
): ProfileInfo => {
const { provider, displayName: name } = profile;
let email = '';
if (profile.emails) {
const [firstEmail] = profile.emails;
email = firstEmail.value;
}
if (!email && params.id_token) {
try {
const decoded: { email: string } = jwtDecoder(params.id_token);
email = decoded.email;
} catch (e) {
console.error('Failed to parse id token and get profile info');
}
}
let picture = '';
if (profile.photos) {
const [firstPhoto] = profile.photos;
picture = firstPhoto.value;
}
return {
provider,
name,
email,
picture,
};
};
export const executeRedirectStrategy = async (
req: express.Request,
@@ -98,6 +134,7 @@ export const executeRefreshTokenStrategy = async (
),
);
}
resolve({
accessToken,
params,
@@ -106,3 +143,24 @@ export const executeRefreshTokenStrategy = async (
);
});
};
export const executeFetchUserProfileStrategy = async (
providerstrategy: passport.Strategy,
accessToken: string,
params: any,
): Promise<ProfileInfo> => {
return new Promise((resolve, reject) => {
const anyStrategy = providerstrategy as any;
anyStrategy.userProfile(
accessToken,
(error: Error, passportProfile: passport.Profile) => {
if (error) {
reject(error);
}
const profile = makeProfileInfo(passportProfile, params);
resolve(profile);
},
);
});
};
@@ -20,6 +20,8 @@ import {
executeFrameHandlerStrategy,
executeRedirectStrategy,
executeRefreshTokenStrategy,
makeProfileInfo,
executeFetchUserProfileStrategy,
} from '../PassportStrategyHelper';
import {
OAuthProviderHandlers,
@@ -27,8 +29,10 @@ import {
AuthInfoPrivate,
RedirectInfo,
AuthProviderConfig,
AuthInfoWithProfile,
} from '../types';
import { OAuthProvider } from '../OAuthProvider';
import passport from 'passport';
export class GoogleAuthProvider implements OAuthProviderHandlers {
private readonly providerConfig: AuthProviderConfig;
@@ -43,13 +47,14 @@ export class GoogleAuthProvider implements OAuthProviderHandlers {
accessToken: any,
refreshToken: any,
params: any,
profile: any,
profile: passport.Profile,
done: any,
) => {
const profileInfo = makeProfileInfo(profile, params);
done(
undefined,
{
profile,
profile: profileInfo,
idToken: params.id_token,
accessToken,
scope: params.scope,
@@ -73,18 +78,28 @@ export class GoogleAuthProvider implements OAuthProviderHandlers {
return await executeFrameHandlerStrategy(req, this._strategy);
}
async refresh(refreshToken: string, scope: string): Promise<AuthInfoBase> {
async refresh(
refreshToken: string,
scope: string,
): Promise<AuthInfoWithProfile> {
const { accessToken, params } = await executeRefreshTokenStrategy(
this._strategy,
refreshToken,
scope,
);
const profile = await executeFetchUserProfileStrategy(
this._strategy,
accessToken,
params,
);
return {
accessToken,
idToken: params.id_token,
expiresInSeconds: params.expires_in,
scope: params.scope,
profile,
};
}
}
+15 -2
View File
@@ -15,7 +15,6 @@
*/
import express from 'express';
import passport from 'passport';
export type AuthProviderConfig = {
provider: string;
@@ -49,7 +48,14 @@ export type AuthInfoBase = {
};
export type AuthInfoWithProfile = AuthInfoBase & {
profile: passport.Profile;
profile:
| {
provider: string;
email: string;
name?: string;
picture?: string;
}
| undefined;
};
export type AuthInfoPrivate = {
@@ -71,6 +77,13 @@ export type RedirectInfo = {
status?: number;
};
export type ProfileInfo = {
provider: string;
email: string;
name: string;
picture: string;
};
export type RefreshTokenResponse = {
accessToken: string;
params: any;