move refresh token response to provider
This commit is contained in:
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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, { CookieOptions } from 'express';
|
||||
import crypto from 'crypto';
|
||||
import { AuthProviderRouteHandlers, OAuthProviderHandlers } from './types';
|
||||
@@ -7,6 +23,69 @@ import { postMessageResponse, ensuresXRequestedWith } from './utils';
|
||||
export const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000;
|
||||
export const TEN_MINUTES_MS = 600 * 1000;
|
||||
|
||||
export const verifyNonce = (req: express.Request, provider: string) => {
|
||||
const cookieNonce = req.cookies[`${provider}-nonce`];
|
||||
const stateNonce = req.query.state;
|
||||
|
||||
if (!cookieNonce || !stateNonce) {
|
||||
throw new Error('Missing nonce');
|
||||
}
|
||||
|
||||
if (cookieNonce !== stateNonce) {
|
||||
throw new Error('Invalid nonce');
|
||||
}
|
||||
};
|
||||
|
||||
export const setNonceCookie = (res: express.Response, provider: string) => {
|
||||
const nonce = crypto.randomBytes(16).toString('base64');
|
||||
|
||||
const options: CookieOptions = {
|
||||
maxAge: TEN_MINUTES_MS,
|
||||
secure: false,
|
||||
sameSite: 'none',
|
||||
domain: 'localhost',
|
||||
path: `/auth/${provider}/handler`,
|
||||
httpOnly: true,
|
||||
};
|
||||
|
||||
res.cookie(`${provider}-nonce`, nonce, options);
|
||||
|
||||
return nonce;
|
||||
};
|
||||
|
||||
export const setRefreshTokenCookie = (
|
||||
res: express.Response,
|
||||
provider: string,
|
||||
refreshToken: string,
|
||||
) => {
|
||||
const options: CookieOptions = {
|
||||
maxAge: THOUSAND_DAYS_MS,
|
||||
secure: false,
|
||||
sameSite: 'none',
|
||||
domain: 'localhost',
|
||||
path: `/auth/${provider}`,
|
||||
httpOnly: true,
|
||||
};
|
||||
|
||||
res.cookie(`${provider}-refresh-token`, refreshToken, options);
|
||||
};
|
||||
|
||||
export const removeRefreshTokenCookie = (
|
||||
res: express.Response,
|
||||
provider: string,
|
||||
) => {
|
||||
const options: CookieOptions = {
|
||||
maxAge: 0,
|
||||
secure: false,
|
||||
sameSite: 'none',
|
||||
domain: 'localhost',
|
||||
path: `/auth/${provider}`,
|
||||
httpOnly: true,
|
||||
};
|
||||
|
||||
res.cookie(`${provider}-refresh-token`, '', options);
|
||||
};
|
||||
|
||||
export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
private readonly provider: string;
|
||||
private readonly providerHandlers: OAuthProviderHandlers;
|
||||
@@ -106,72 +185,9 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
refreshToken,
|
||||
scope,
|
||||
);
|
||||
res.send(refreshInfo);
|
||||
return res.send(refreshInfo);
|
||||
} catch (error) {
|
||||
res.status(401).send(`${error.message}`);
|
||||
return res.status(401).send(`${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const verifyNonce = (req: express.Request, provider: string) => {
|
||||
const cookieNonce = req.cookies[`${provider}-nonce`];
|
||||
const stateNonce = req.query.state;
|
||||
|
||||
if (!cookieNonce || !stateNonce) {
|
||||
throw new Error('Missing nonce');
|
||||
}
|
||||
|
||||
if (cookieNonce !== stateNonce) {
|
||||
throw new Error('Invalid nonce');
|
||||
}
|
||||
};
|
||||
|
||||
export const setNonceCookie = (res: express.Response, provider: string) => {
|
||||
const nonce = crypto.randomBytes(16).toString('base64');
|
||||
|
||||
const options: CookieOptions = {
|
||||
maxAge: TEN_MINUTES_MS,
|
||||
secure: false,
|
||||
sameSite: 'none',
|
||||
domain: 'localhost',
|
||||
path: `/auth/${provider}/handler`,
|
||||
httpOnly: true,
|
||||
};
|
||||
|
||||
res.cookie(`${provider}-nonce`, nonce, options);
|
||||
|
||||
return nonce;
|
||||
};
|
||||
|
||||
export const setRefreshTokenCookie = (
|
||||
res: express.Response,
|
||||
provider: string,
|
||||
refreshToken: string,
|
||||
) => {
|
||||
const options: CookieOptions = {
|
||||
maxAge: THOUSAND_DAYS_MS,
|
||||
secure: false,
|
||||
sameSite: 'none',
|
||||
domain: 'localhost',
|
||||
path: `/auth/${provider}`,
|
||||
httpOnly: true,
|
||||
};
|
||||
|
||||
res.cookie(`${provider}-refresh-token`, refreshToken, options);
|
||||
};
|
||||
|
||||
export const removeRefreshTokenCookie = (
|
||||
res: express.Response,
|
||||
provider: string,
|
||||
) => {
|
||||
const options: CookieOptions = {
|
||||
maxAge: 0,
|
||||
secure: false,
|
||||
sameSite: 'none',
|
||||
domain: 'localhost',
|
||||
path: `/auth/${provider}`,
|
||||
httpOnly: true,
|
||||
};
|
||||
|
||||
res.cookie(`${provider}-refresh-token`, '', options);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
import express, { CookieOptions } from 'express';
|
||||
/*
|
||||
* 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 passport from 'passport';
|
||||
import { RedirectInfo, AuthInfoBase } from './types';
|
||||
import { RedirectInfo, RefreshTokenResponse } from './types';
|
||||
|
||||
export const executeRedirectStrategy = async (
|
||||
req: express.Request,
|
||||
@@ -28,7 +44,7 @@ export const executeFrameHandlerStrategy = async (
|
||||
};
|
||||
strategy.fail = (
|
||||
info: { type: 'success' | 'error'; message?: string },
|
||||
_status?: number,
|
||||
// _status: number,
|
||||
) => {
|
||||
reject(new Error(`Authentication rejected, ${info.message ?? ''}`));
|
||||
};
|
||||
@@ -47,7 +63,7 @@ export const executeRefreshTokenStrategy = async (
|
||||
providerstrategy: passport.Strategy,
|
||||
refreshToken: string,
|
||||
scope: string,
|
||||
): Promise<AuthInfoBase> => {
|
||||
): Promise<RefreshTokenResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const anyStrategy = providerstrategy as any;
|
||||
const OAuth2 = anyStrategy._oauth2.constructor;
|
||||
@@ -84,9 +100,7 @@ export const executeRefreshTokenStrategy = async (
|
||||
}
|
||||
resolve({
|
||||
accessToken,
|
||||
idToken: params.id_token,
|
||||
expiresInSeconds: 10,
|
||||
scope: params.scope,
|
||||
params,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1 +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 { GoogleAuthProvider } from './provider';
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 GoogleStrategy } from 'passport-google-oauth20';
|
||||
import {
|
||||
@@ -14,12 +30,10 @@ import {
|
||||
} from '../types';
|
||||
|
||||
export class GoogleAuthProvider implements OAuthProviderHandlers {
|
||||
private readonly provider: string;
|
||||
private readonly providerConfig: AuthProviderConfig;
|
||||
private readonly _strategy: GoogleStrategy;
|
||||
|
||||
constructor(providerConfig: AuthProviderConfig) {
|
||||
this.provider = providerConfig.provider;
|
||||
this.providerConfig = providerConfig;
|
||||
// TODO: throw error if env variables not set?
|
||||
this._strategy = new GoogleStrategy(
|
||||
@@ -38,7 +52,7 @@ export class GoogleAuthProvider implements OAuthProviderHandlers {
|
||||
idToken: params.id_token,
|
||||
accessToken,
|
||||
scope: params.scope,
|
||||
expiresInSeconds: 10,
|
||||
expiresInSeconds: params.expires_in,
|
||||
},
|
||||
{
|
||||
refreshToken,
|
||||
@@ -59,10 +73,17 @@ export class GoogleAuthProvider implements OAuthProviderHandlers {
|
||||
}
|
||||
|
||||
async refresh(refreshToken: string, scope: string): Promise<AuthInfoBase> {
|
||||
return await executeRefreshTokenStrategy(
|
||||
const { accessToken, params } = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
refreshToken,
|
||||
scope,
|
||||
);
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
idToken: params.id_token,
|
||||
expiresInSeconds: params.expires_in,
|
||||
scope: params.scope,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,3 +73,8 @@ export type RedirectInfo = {
|
||||
url: string;
|
||||
status?: number;
|
||||
};
|
||||
|
||||
export type RefreshTokenResponse = {
|
||||
accessToken: string;
|
||||
params: any;
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ export async function createRouter(
|
||||
// configure all the providers
|
||||
for (const providerConfig of providers) {
|
||||
const { providerId, providerRouter } = makeProvider(providerConfig);
|
||||
logger.info(`Configuring provider, ${providerId}`);
|
||||
router.use(`/${providerId}`, providerRouter);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user