@@ -157,25 +157,47 @@ export type ProfileInfoOptions = {
|
||||
optional?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* This API provides access to profile information of the user from an auth provider.
|
||||
*/
|
||||
export type ProfileInfoApi = {
|
||||
getProfile(options?: ProfileInfoOptions): Promise<ProfileInfo | undefined>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Profile information of the user from an auth provider.
|
||||
*/
|
||||
export type ProfileInfo = {
|
||||
provider: string;
|
||||
/**
|
||||
* Email ID.
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* Display name that can be presented to the user.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* URL to an avatar image of the user.
|
||||
*/
|
||||
picture?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Session state values passed to subscribers of the SessionStateApi.
|
||||
*/
|
||||
export enum SessionState {
|
||||
SignedIn = 'SignedIn',
|
||||
SignedOut = 'SignedOut',
|
||||
}
|
||||
|
||||
/**
|
||||
* This API provides access to an sessionState$ observable which provides an update when the
|
||||
* user performs a sign in or sign out from an auth provider.
|
||||
*/
|
||||
export type SessionStateApi = {
|
||||
sessionState$(): Observable<SessionState>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides authentication towards Google APIs and identities.
|
||||
*
|
||||
|
||||
@@ -57,6 +57,8 @@ export class EnvironmentHandler implements AuthProviderRouteHandlers {
|
||||
|
||||
async logout(req: express.Request, res: express.Response): Promise<void> {
|
||||
const provider = this.getProviderForEnv(req);
|
||||
await provider.logout(req, res);
|
||||
if (provider.logout) {
|
||||
await provider.logout(req, res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,14 @@ import {
|
||||
RedirectInfo,
|
||||
RefreshTokenResponse,
|
||||
ProfileInfo,
|
||||
ProviderStrategy,
|
||||
} from '../providers/types';
|
||||
|
||||
export const makeProfileInfo = (
|
||||
profile: passport.Profile,
|
||||
params: any,
|
||||
): ProfileInfo => {
|
||||
const { provider, displayName: name } = profile;
|
||||
const { displayName: name } = profile;
|
||||
|
||||
let email = '';
|
||||
if (profile.emails) {
|
||||
@@ -51,7 +52,6 @@ export const makeProfileInfo = (
|
||||
}
|
||||
|
||||
return {
|
||||
provider,
|
||||
name,
|
||||
email,
|
||||
picture,
|
||||
@@ -100,12 +100,12 @@ export const executeFrameHandlerStrategy = async (
|
||||
};
|
||||
|
||||
export const executeRefreshTokenStrategy = async (
|
||||
providerstrategy: passport.Strategy,
|
||||
providerStrategy: passport.Strategy,
|
||||
refreshToken: string,
|
||||
scope: string,
|
||||
): Promise<RefreshTokenResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const anyStrategy = providerstrategy as any;
|
||||
const anyStrategy = providerStrategy as any;
|
||||
const OAuth2 = anyStrategy._oauth2.constructor;
|
||||
const oauth2 = new OAuth2(
|
||||
anyStrategy._oauth2._clientId,
|
||||
@@ -149,12 +149,12 @@ export const executeRefreshTokenStrategy = async (
|
||||
};
|
||||
|
||||
export const executeFetchUserProfileStrategy = async (
|
||||
providerstrategy: passport.Strategy,
|
||||
providerStrategy: passport.Strategy,
|
||||
accessToken: string,
|
||||
params: any,
|
||||
): Promise<ProfileInfo> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const anyStrategy = providerstrategy as any;
|
||||
const anyStrategy = (providerStrategy as unknown) as ProviderStrategy;
|
||||
anyStrategy.userProfile(
|
||||
accessToken,
|
||||
(error: Error, passportProfile: passport.Profile) => {
|
||||
|
||||
@@ -44,7 +44,9 @@ export const createAuthProviderRouter = (
|
||||
router.get('/start', provider.start.bind(provider));
|
||||
router.get('/handler/frame', provider.frameHandler.bind(provider));
|
||||
router.post('/handler/frame', provider.frameHandler.bind(provider));
|
||||
router.post('/logout', provider.logout.bind(provider));
|
||||
if (provider.logout) {
|
||||
router.post('/logout', provider.logout.bind(provider));
|
||||
}
|
||||
if (provider.refresh) {
|
||||
router.get('/refresh', provider.refresh.bind(provider));
|
||||
}
|
||||
|
||||
@@ -18,48 +18,163 @@ import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export type OAuthProviderOptions = {
|
||||
/**
|
||||
* Client ID of the auth provider.
|
||||
*/
|
||||
clientID: string;
|
||||
/**
|
||||
* Client Secret of the auth provider.
|
||||
*/
|
||||
clientSecret: string;
|
||||
/**
|
||||
* Callback URL to be passed to the auth provider to redirect to after the user signs in.
|
||||
*/
|
||||
callbackURL: string;
|
||||
};
|
||||
|
||||
export type SAMLProviderConfig = {
|
||||
entryPoint: string;
|
||||
issuer: string;
|
||||
};
|
||||
|
||||
export type EnvironmentProviderConfig = {
|
||||
[key: string]: OAuthProviderConfig | SAMLProviderConfig;
|
||||
};
|
||||
|
||||
export type AuthProviderConfig = {
|
||||
baseUrl: string;
|
||||
};
|
||||
|
||||
export type OAuthProviderConfig = {
|
||||
/**
|
||||
* Cookies can be marked with a secure flag to send cookies only when the request
|
||||
* is over an encrypted channel (HTTPS).
|
||||
*
|
||||
* For development environment we don't mark the cookie as secure since we serve
|
||||
* localhost over HTTP.
|
||||
*/
|
||||
secure: boolean;
|
||||
appOrigin: string; // http://localhost:3000
|
||||
/**
|
||||
* The protocol://domain[:port] where the app (frontend) is hosted. This is used to post messages back
|
||||
* to the window that initiates an auth request.
|
||||
*/
|
||||
appOrigin: string;
|
||||
/**
|
||||
* Client ID of the auth provider.
|
||||
*/
|
||||
clientId: string;
|
||||
/**
|
||||
* Client Secret of the auth provider.
|
||||
*/
|
||||
clientSecret: string;
|
||||
};
|
||||
|
||||
export type EnvironmentProviderConfig = {
|
||||
/**
|
||||
* key, values are environment names and OAuthProviderConfigs
|
||||
*
|
||||
* For e.g
|
||||
* {
|
||||
* development: DevelopmentOAuthProviderConfig
|
||||
* production: ProductionOAuthProviderConfig
|
||||
* }
|
||||
*/
|
||||
[key: string]: OAuthProviderConfig;
|
||||
};
|
||||
|
||||
export type AuthProviderConfig = {
|
||||
/**
|
||||
* The protocol://domain[:port] where the app is hosted. This is used to construct the
|
||||
* callbackURL to redirect to once the user signs in to the auth provider.
|
||||
*/
|
||||
baseUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Any OAuth provider needs to implement this interface which has provider specific
|
||||
* handlers for different methods to perform authentication, get access tokens,
|
||||
* refresh tokens and perform sign out.
|
||||
*/
|
||||
export interface OAuthProviderHandlers {
|
||||
/**
|
||||
* This method initiates a sign in request with an auth provider.
|
||||
* @param {express.Request} req
|
||||
* @param options
|
||||
*/
|
||||
start(req: express.Request, options: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Handles the redirect from the auth provider when the user has signed in.
|
||||
* @param {express.Request} req
|
||||
*/
|
||||
handler(req: express.Request): Promise<any>;
|
||||
|
||||
/**
|
||||
* (Optional) Given a refresh token and scope fetches a new access token from the auth provider.
|
||||
* @param {string} refreshToken
|
||||
* @param {string} scope
|
||||
*/
|
||||
refresh?(refreshToken: string, scope: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* (Optional) Sign out of the auth provider.
|
||||
*/
|
||||
logout?(): Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any Auth provider needs to implement this interface which handles the routes in the
|
||||
* auth backend. Any auth API requests from the frontend reaches these methods.
|
||||
*
|
||||
* The routes in the auth backend API are tied to these methods like below
|
||||
*
|
||||
* /auth/[provider]/start -> start
|
||||
* /auth/[provider]/handler/frame -> frameHandler
|
||||
* /auth/[provider]/refresh -> refresh
|
||||
* /auth/[provider]/logout -> logout
|
||||
*/
|
||||
export interface AuthProviderRouteHandlers {
|
||||
/**
|
||||
* Handles the start route of the API. This initiates a sign in request with an auth provider.
|
||||
*
|
||||
* Request
|
||||
* - scopes for the auth request (Optional)
|
||||
* Response
|
||||
* - redirect to the auth provider for the user to sign in or consent.
|
||||
* - sets a nonce cookie and also pass the nonce as 'state' query parameter in the redirect request
|
||||
*
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
start(req: express.Request, res: express.Response): Promise<any>;
|
||||
frameHandler(req: express.Request, res: express.Response): Promise<any>;
|
||||
refresh?(req: express.Request, res: express.Response): Promise<any>;
|
||||
logout(req: express.Request, res: express.Response): Promise<any>;
|
||||
}
|
||||
|
||||
export type SAMLEnvironmentProviderConfig = {
|
||||
[key: string]: SAMLProviderConfig;
|
||||
};
|
||||
/**
|
||||
* Once the user signs in or consents in the OAuth screen, the auth provider redirects to the
|
||||
* callbackURL which is handled by this method.
|
||||
*
|
||||
* Request
|
||||
* - to contain a nonce cookie and a 'state' query parameter
|
||||
* Response
|
||||
* - postMessage to the window with a payload that contains accessToken, expiryInSeconds?, idToken? and scope.
|
||||
* - sets a refresh token cookie if the auth provider supports refresh tokens
|
||||
*
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
frameHandler(req: express.Request, res: express.Response): Promise<any>;
|
||||
|
||||
/**
|
||||
* (Optional) If the auth provider supports refresh tokens then this method handles
|
||||
* requests to get a new access token.
|
||||
*
|
||||
* Request
|
||||
* - to contain a refresh token cookie and scope (Optional) query parameter.
|
||||
* Response
|
||||
* - payload with accessToken, expiryInSeconds?, idToken?, scope and user profile information.
|
||||
*
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
refresh?(req: express.Request, res: express.Response): Promise<any>;
|
||||
|
||||
/**
|
||||
* (Optional) Handles sign out requests
|
||||
*
|
||||
* Response
|
||||
* - removes the refresh token cookie
|
||||
*
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
logout?(req: express.Request, res: express.Response): Promise<any>;
|
||||
}
|
||||
|
||||
export type AuthProviderFactory = (
|
||||
globalConfig: AuthProviderConfig,
|
||||
@@ -68,27 +183,42 @@ export type AuthProviderFactory = (
|
||||
) => AuthProviderRouteHandlers;
|
||||
|
||||
export type AuthInfoBase = {
|
||||
/**
|
||||
* An access token issued for the signed in user.
|
||||
*/
|
||||
accessToken: string;
|
||||
/**
|
||||
* (Optional) Id token issued for the signed in user.
|
||||
*/
|
||||
idToken?: string;
|
||||
/**
|
||||
* Expiry of the access token in seconds.
|
||||
*/
|
||||
expiresInSeconds?: number;
|
||||
/**
|
||||
* Scopes granted for the access token.
|
||||
*/
|
||||
scope: string;
|
||||
};
|
||||
|
||||
export type AuthInfoWithProfile = AuthInfoBase & {
|
||||
profile:
|
||||
| {
|
||||
provider: string;
|
||||
email: string;
|
||||
name?: string;
|
||||
picture?: string;
|
||||
}
|
||||
| undefined;
|
||||
/**
|
||||
* Profile information of the signed in user.
|
||||
*/
|
||||
profile: ProfileInfo | undefined;
|
||||
};
|
||||
|
||||
export type AuthInfoPrivate = {
|
||||
/**
|
||||
* A refresh token issued for the signed in user.
|
||||
*/
|
||||
refreshToken: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Payload sent as a post message after the auth request is complete.
|
||||
* If successful then has a valid payload with Auth information else contains an error.
|
||||
*/
|
||||
export type AuthResponse =
|
||||
| {
|
||||
type: 'auth-result';
|
||||
@@ -100,18 +230,49 @@ export type AuthResponse =
|
||||
};
|
||||
|
||||
export type RedirectInfo = {
|
||||
/**
|
||||
* URL to redirect to
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Status code to use for the redirect
|
||||
*/
|
||||
status?: number;
|
||||
};
|
||||
|
||||
export type ProfileInfo = {
|
||||
provider: string;
|
||||
/**
|
||||
* Email ID of the signed in user.
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* Display name that can be presented to the signed in user.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* URL to an image that can be used as the display image or avatar of the
|
||||
* signed in user.
|
||||
*/
|
||||
picture: string;
|
||||
};
|
||||
|
||||
export type RefreshTokenResponse = {
|
||||
/**
|
||||
* An access token issued for the signed in user.
|
||||
*/
|
||||
accessToken: string;
|
||||
params: any;
|
||||
};
|
||||
|
||||
export type ProviderStrategy = {
|
||||
userProfile(accessToken: string, callback: Function): void;
|
||||
};
|
||||
|
||||
export type SAMLProviderConfig = {
|
||||
entryPoint: string;
|
||||
issuer: string;
|
||||
};
|
||||
|
||||
export type SAMLEnvironmentProviderConfig = {
|
||||
[key: string]: SAMLProviderConfig;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user