move refresh token response to provider

This commit is contained in:
Raghunandan
2020-05-28 22:06:53 +02:00
parent c35c9d8023
commit d16dfdc55b
6 changed files with 149 additions and 76 deletions
@@ -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);
}