remove tests. all routes for google auth working
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
import express from 'express';
|
||||
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
||||
import {
|
||||
executeFrameHandlerStrategy,
|
||||
executeRedirectStrategy,
|
||||
executeRefreshTokenStrategy,
|
||||
} from './PassportStrategyHelper';
|
||||
import {
|
||||
OAuthProviderHandlers,
|
||||
AuthInfoBase,
|
||||
AuthInfoPrivate,
|
||||
RedirectInfo,
|
||||
AuthProviderConfig,
|
||||
} 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(
|
||||
{ ...this.providerConfig.options },
|
||||
(
|
||||
accessToken: any,
|
||||
refreshToken: any,
|
||||
params: any,
|
||||
profile: any,
|
||||
done: any,
|
||||
) => {
|
||||
done(
|
||||
undefined,
|
||||
{
|
||||
profile,
|
||||
idToken: params.id_token,
|
||||
accessToken,
|
||||
scope: params.scope,
|
||||
expiresInSeconds: params.expires_in,
|
||||
},
|
||||
{
|
||||
refreshToken,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async refresh(refreshToken: string, scope: string): Promise<AuthInfoBase> {
|
||||
return await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
refreshToken,
|
||||
scope,
|
||||
);
|
||||
}
|
||||
|
||||
logout(): Promise<any> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getProvider(): string {
|
||||
return this.provider;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import express, { CookieOptions } from 'express';
|
||||
import crypto from 'crypto';
|
||||
|
||||
export const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000;
|
||||
export const TEN_MINUTES_MS = 600 * 1000;
|
||||
|
||||
// TODO: move all of these methods to OAuthProvider
|
||||
|
||||
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,14 +1,12 @@
|
||||
import express, { CookieOptions } from 'express';
|
||||
import crypto from 'crypto';
|
||||
import { AuthProviderRouteHandlers, OAuthProviderHandlers } from './types';
|
||||
import express from 'express';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import {
|
||||
setNonceCookie,
|
||||
verifyNonce,
|
||||
setRefreshTokenCookie,
|
||||
removeRefreshTokenCookie,
|
||||
} from './OAuthHelper';
|
||||
import { postMessageResponse, ensuresXRequestedWith } from './utils';
|
||||
|
||||
export const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000;
|
||||
export const TEN_MINUTES_MS = 600 * 1000;
|
||||
|
||||
export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
private readonly provider: string;
|
||||
private readonly providerHandlers: OAuthProviderHandlers;
|
||||
@@ -114,3 +112,66 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@ export const executeRefreshTokenStrategy = async (
|
||||
resolve({
|
||||
accessToken,
|
||||
idToken: params.id_token,
|
||||
expiresInSeconds: params.expires_in,
|
||||
expiresInSeconds: 10,
|
||||
scope: params.scope,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// /*
|
||||
// * 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 { OAuthProviderHandlers } from './types';
|
||||
// import { ProviderFactories } from './factories';
|
||||
|
||||
// class MyAuthProvider implements OAuthProviderHandlers {
|
||||
// async start(_: express.Request, res: express.Response): Promise<any> {
|
||||
// res.send('start');
|
||||
// }
|
||||
// async logout(_: express.Request, res: express.Response): Promise<any> {
|
||||
// res.send('logout');
|
||||
// }
|
||||
// async handler(): Promise<any> {
|
||||
// throw new Error('Method not implemented.');
|
||||
// }
|
||||
// async refresh(): Promise<any> {
|
||||
// throw new Error('Method not implemented.');
|
||||
// }
|
||||
// }
|
||||
|
||||
// describe('getProviderFactory', () => {
|
||||
// it('makes a provider for MyAuthProvider', () => {
|
||||
// jest
|
||||
// .spyOn(ProviderFactories, 'getProviderFactory')
|
||||
// .mockReturnValueOnce(MyAuthProvider);
|
||||
// const provider = ProviderFactories.getProviderFactory('a');
|
||||
// expect(provider).toBeDefined();
|
||||
// });
|
||||
|
||||
// it('throws an error when provider implementation does not exist', () => {
|
||||
// expect(() => {
|
||||
// ProviderFactories.getProviderFactory('b');
|
||||
// }).toThrow('Provider Implementation missing for : b auth provider');
|
||||
// });
|
||||
// });
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
|
||||
import { AuthProviderFactories, AuthProviderFactory } from './types';
|
||||
// import { GoogleAuthProvider } from './google/provider';
|
||||
import { GoogleAuthProvider } from './GoogleAuthProvider';
|
||||
import { GoogleAuthProvider } from './google';
|
||||
|
||||
export class ProviderFactories {
|
||||
private static readonly providerFactories: AuthProviderFactories = {
|
||||
|
||||
@@ -1 +1 @@
|
||||
// export { GoogleAuthProvider } from './provider';
|
||||
export { GoogleAuthProvider } from './provider';
|
||||
|
||||
@@ -1,531 +0,0 @@
|
||||
// /*
|
||||
// * 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 {
|
||||
// GoogleAuthProvider,
|
||||
// THOUSAND_DAYS_MS,
|
||||
// TEN_MINUTES_MS,
|
||||
// } from './provider';
|
||||
// import passport from 'passport';
|
||||
// import express from 'express';
|
||||
// import * as utils from './../utils';
|
||||
// import refresh from 'passport-oauth2-refresh';
|
||||
|
||||
// const googleAuthProviderConfig = {
|
||||
// provider: 'google',
|
||||
// options: {
|
||||
// clientID: 'a',
|
||||
// clientSecret: 'b',
|
||||
// callbackURL: 'c',
|
||||
// },
|
||||
// };
|
||||
|
||||
// const googleAuthProviderConfigInvalidOptions = {
|
||||
// provider: 'google',
|
||||
// options: {},
|
||||
// };
|
||||
|
||||
// describe('GoogleAuthProvider', () => {
|
||||
// afterEach(() => {
|
||||
// jest.clearAllMocks();
|
||||
// });
|
||||
// describe('create a new provider', () => {
|
||||
// it('should succeed with valid config', () => {
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
// expect(googleAuthProvider).toBeDefined();
|
||||
// expect(googleAuthProvider.start).toBeDefined();
|
||||
// expect(googleAuthProvider.logout).toBeDefined();
|
||||
// expect(googleAuthProvider.frameHandler).toBeDefined();
|
||||
// expect(googleAuthProvider.strategy).toBeDefined();
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('start authentication handler', () => {
|
||||
// const mockResponse = ({
|
||||
// send: jest.fn().mockReturnThis(),
|
||||
// status: jest.fn().mockReturnThis(),
|
||||
// cookie: jest.fn().mockReturnThis(),
|
||||
// } as unknown) as express.Response;
|
||||
|
||||
// fit('should initiate authenticate request with provided scopes', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// query: {
|
||||
// scope: 'a,b',
|
||||
// },
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// // const spyPassport = jest
|
||||
// // .spyOn(passport, 'authenticate')
|
||||
// // .mockImplementation(() => jest.fn());
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// const googleAuthProviderStrategy = googleAuthProvider.strategy();
|
||||
// const spyAuthenticate = jest
|
||||
// .spyOn(googleAuthProviderStrategy, 'authenticate')
|
||||
// .mockImplementation(() => jest.fn());
|
||||
|
||||
// // const spyRedirect = jest
|
||||
// // .spyOn(googleAuthProviderStrategy, 'redirect')
|
||||
// // .mockImplementation(() => jest.fn());
|
||||
|
||||
// googleAuthProvider.start(mockRequest, mockResponse);
|
||||
// expect(spyAuthenticate).toBeCalledTimes(1);
|
||||
// expect(spyAuthenticate).toBeCalledWith(mockRequest, {
|
||||
// scope: 'a,b',
|
||||
// accessType: 'offline',
|
||||
// prompt: 'consent',
|
||||
// state: expect.any(String),
|
||||
// });
|
||||
// // expect(spyRedirect).toBeCalledTimes(1);
|
||||
// // expect(spyPassport).toBeCalledTimes(1);
|
||||
// });
|
||||
|
||||
// it('should set a nonce cookie', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// query: {
|
||||
// scope: 'a,b',
|
||||
// },
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
// googleAuthProvider.start(mockRequest, mockResponse);
|
||||
// expect(mockResponse.cookie).toBeCalledTimes(1);
|
||||
// expect(mockResponse.cookie).toBeCalledWith(
|
||||
// 'google-nonce',
|
||||
// expect.any(String),
|
||||
// expect.objectContaining({
|
||||
// maxAge: TEN_MINUTES_MS,
|
||||
// path: `/auth/${googleAuthProviderConfig.provider}/handler`,
|
||||
// }),
|
||||
// );
|
||||
// });
|
||||
|
||||
// it('should throw error if no scopes provided', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// query: {},
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
// expect(() => {
|
||||
// googleAuthProvider.start(mockRequest, mockResponse);
|
||||
// }).toThrowError('missing scope parameter');
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('logout handler', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// it('should perform logout and respond with 200', () => {
|
||||
// const mockResponse: any = ({
|
||||
// send: jest.fn(),
|
||||
// cookie: jest.fn(),
|
||||
// } as unknown) as express.Response;
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// const spyResponse = jest
|
||||
// .spyOn(mockResponse, 'send')
|
||||
// .mockImplementation(() => jest.fn());
|
||||
|
||||
// googleAuthProvider.logout(mockRequest, mockResponse);
|
||||
// expect(spyResponse).toBeCalledTimes(1);
|
||||
// expect(spyResponse).toBeCalledWith('logout!');
|
||||
// expect(mockResponse.cookie).toBeCalledTimes(1);
|
||||
// expect(mockResponse.cookie).toBeCalledWith(
|
||||
// 'google-refresh-token',
|
||||
// '',
|
||||
// expect.objectContaining({ maxAge: 0 }),
|
||||
// );
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('redirect frame handler', () => {
|
||||
// const mockResponse: any = ({
|
||||
// status: jest.fn().mockReturnThis(),
|
||||
// send: jest.fn().mockReturnThis(),
|
||||
// cookie: jest.fn().mockReturnThis(),
|
||||
// } as unknown) as express.Response;
|
||||
|
||||
// it('should call authenticate and post a response', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// cookies: { 'google-nonce': 'NONCE' },
|
||||
// query: {
|
||||
// state: 'NONCE',
|
||||
// },
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const spyPostMessage = jest
|
||||
// .spyOn(utils, 'postMessageResponse')
|
||||
// .mockImplementation(() => jest.fn());
|
||||
|
||||
// const spyPassport = jest
|
||||
// .spyOn(passport, 'authenticate')
|
||||
// .mockImplementation((_x, callbackFunc) => {
|
||||
// const cb = callbackFunc as Function;
|
||||
// cb(null, { refreshToken: 'REFRESH_TOKEN' });
|
||||
// return jest.fn();
|
||||
// });
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// googleAuthProvider.frameHandler(mockRequest, mockResponse);
|
||||
// expect(spyPassport).toBeCalledTimes(1);
|
||||
// expect(spyPostMessage).toBeCalledTimes(1);
|
||||
// expect(mockResponse.cookie).toBeCalledTimes(1);
|
||||
// expect(mockResponse.cookie).toBeCalledWith(
|
||||
// 'google-refresh-token',
|
||||
// 'REFRESH_TOKEN',
|
||||
// expect.objectContaining({
|
||||
// path: '/auth/google',
|
||||
// sameSite: 'none',
|
||||
// httpOnly: true,
|
||||
// maxAge: THOUSAND_DAYS_MS,
|
||||
// }),
|
||||
// );
|
||||
// });
|
||||
|
||||
// it('should respond with a error message if no refresh token returned', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// cookies: { 'google-nonce': 'NONCE' },
|
||||
// query: {
|
||||
// state: 'NONCE',
|
||||
// },
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const spyPassport = jest
|
||||
// .spyOn(passport, 'authenticate')
|
||||
// .mockImplementation((_x, callbackFunc) => {
|
||||
// const cb = callbackFunc as Function;
|
||||
// cb(null, {});
|
||||
// return jest.fn();
|
||||
// });
|
||||
|
||||
// const spyPostMessage = jest
|
||||
// .spyOn(utils, 'postMessageResponse')
|
||||
// .mockImplementation(() => jest.fn());
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// googleAuthProvider.frameHandler(mockRequest, mockResponse);
|
||||
// expect(spyPassport).toBeCalledTimes(1);
|
||||
// expect(spyPostMessage).toBeCalledTimes(1);
|
||||
// expect(spyPostMessage).toBeCalledWith(mockResponse, {
|
||||
// type: 'auth-result',
|
||||
// error: new Error('Missing refresh token'),
|
||||
// });
|
||||
// });
|
||||
|
||||
// it('should respond with a error message if auth failed', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// cookies: { 'google-nonce': 'NONCE' },
|
||||
// query: {
|
||||
// state: 'NONCE',
|
||||
// },
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const spyPassport = jest
|
||||
// .spyOn(passport, 'authenticate')
|
||||
// .mockImplementation((_x, callbackFunc) => {
|
||||
// const cb = callbackFunc as Function;
|
||||
// cb(new Error('TokenError'), null);
|
||||
// return jest.fn();
|
||||
// });
|
||||
|
||||
// const spyPostMessage = jest
|
||||
// .spyOn(utils, 'postMessageResponse')
|
||||
// .mockImplementation(() => jest.fn());
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// googleAuthProvider.frameHandler(mockRequest, mockResponse);
|
||||
// expect(spyPassport).toBeCalledTimes(1);
|
||||
// expect(spyPostMessage).toBeCalledTimes(1);
|
||||
// expect(spyPostMessage).toBeCalledWith(mockResponse, {
|
||||
// type: 'auth-result',
|
||||
// error: new Error('Google auth failed, Error: TokenError'),
|
||||
// });
|
||||
// });
|
||||
|
||||
// it('should respond with a error message if cookie nonce is missing', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// cookies: {},
|
||||
// query: { state: 'NONCE' },
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// googleAuthProvider.frameHandler(mockRequest, mockResponse);
|
||||
// expect(mockResponse.send).toBeCalledTimes(1);
|
||||
// expect(mockResponse.send).toBeCalledWith('Missing nonce');
|
||||
// expect(mockResponse.status).toBeCalledTimes(1);
|
||||
// expect(mockResponse.status).toBeCalledWith(401);
|
||||
// });
|
||||
|
||||
// it('should respond with a error message if state nonce is missing', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// cookies: { 'google-nonce': 'NONCE' },
|
||||
// query: {},
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// googleAuthProvider.frameHandler(mockRequest, mockResponse);
|
||||
// expect(mockResponse.send).toBeCalledTimes(1);
|
||||
// expect(mockResponse.send).toBeCalledWith('Missing nonce');
|
||||
// expect(mockResponse.status).toBeCalledTimes(1);
|
||||
// expect(mockResponse.status).toBeCalledWith(401);
|
||||
// });
|
||||
|
||||
// it('should respond with a error message if nonce mismatch', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// cookies: { 'google-nonce': 'NONCA' },
|
||||
// query: { state: 'NONCEB' },
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// googleAuthProvider.frameHandler(mockRequest, mockResponse);
|
||||
// expect(mockResponse.send).toBeCalledTimes(1);
|
||||
// expect(mockResponse.send).toBeCalledWith('Invalid nonce');
|
||||
// expect(mockResponse.status).toBeCalledTimes(1);
|
||||
// expect(mockResponse.status).toBeCalledWith(401);
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('strategy handler', () => {
|
||||
// it('should return a valid passport strategy', () => {
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// expect(googleAuthProvider.strategy()).toBeInstanceOf(passport.Strategy);
|
||||
// });
|
||||
|
||||
// it('should throw an error for invalid options', () => {
|
||||
// expect(() => {
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfigInvalidOptions,
|
||||
// );
|
||||
// googleAuthProvider.strategy();
|
||||
// }).toThrow();
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('refresh token handler', () => {
|
||||
// const mockResponse = ({
|
||||
// status: jest.fn().mockReturnThis(),
|
||||
// send: jest.fn().mockReturnThis(),
|
||||
// } as unknown) as express.Response;
|
||||
|
||||
// describe('no refresh token cookie', () => {
|
||||
// it('should respond with a 401', () => {
|
||||
// const mockRequest = ({
|
||||
// cookies: jest.fn(),
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// googleAuthProvider.refresh(mockRequest, mockResponse);
|
||||
// expect(mockResponse.send).toBeCalledTimes(1);
|
||||
// expect(mockResponse.send).toBeCalledWith('Missing session cookie');
|
||||
|
||||
// expect(mockResponse.status).toBeCalledTimes(1);
|
||||
// expect(mockResponse.status).toBeCalledWith(401);
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('refresh token cookie, no scope', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// cookies: { 'google-refresh-token': 'REFRESH_TOKEN' },
|
||||
// query: {},
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// it('should request for a new access token and fail if no access token returned', () => {
|
||||
// const spyRefresh = jest
|
||||
// .spyOn(refresh, 'requestNewAccessToken')
|
||||
// .mockImplementation((_x, _y, _z, callbackFunc) => {
|
||||
// const cb = callbackFunc as Function;
|
||||
// cb(undefined, undefined, undefined, {});
|
||||
// });
|
||||
|
||||
// googleAuthProvider.refresh(mockRequest, mockResponse);
|
||||
// expect(spyRefresh).toBeCalledTimes(1);
|
||||
// expect(spyRefresh).toBeCalledWith(
|
||||
// 'google',
|
||||
// 'REFRESH_TOKEN',
|
||||
// {},
|
||||
// expect.any(Function),
|
||||
// );
|
||||
// expect(mockResponse.status).toBeCalledTimes(1);
|
||||
// expect(mockResponse.status).toBeCalledWith(401);
|
||||
// expect(mockResponse.send).toBeCalledTimes(1);
|
||||
// expect(mockResponse.send).toBeCalledWith(
|
||||
// 'Failed to refresh access token',
|
||||
// );
|
||||
// });
|
||||
|
||||
// it('should request for a new access token and return 401 if any error', () => {
|
||||
// const spyRefresh = jest
|
||||
// .spyOn(refresh, 'requestNewAccessToken')
|
||||
// .mockImplementation((_x, _y, _z, callbackFunc) => {
|
||||
// const cb = callbackFunc as Function;
|
||||
// cb({ error: 'ERROR' }, undefined, undefined, {});
|
||||
// });
|
||||
|
||||
// googleAuthProvider.refresh(mockRequest, mockResponse);
|
||||
// expect(spyRefresh).toBeCalledTimes(1);
|
||||
// expect(spyRefresh).toBeCalledWith(
|
||||
// 'google',
|
||||
// 'REFRESH_TOKEN',
|
||||
// {},
|
||||
// expect.any(Function),
|
||||
// );
|
||||
// expect(mockResponse.status).toBeCalledTimes(1);
|
||||
// expect(mockResponse.status).toBeCalledWith(401);
|
||||
// expect(mockResponse.send).toBeCalledTimes(1);
|
||||
// expect(mockResponse.send).toBeCalledWith(
|
||||
// 'Failed to refresh access token',
|
||||
// );
|
||||
// });
|
||||
|
||||
// it('should fetch and return a new access token', () => {
|
||||
// const spyRefresh = jest
|
||||
// .spyOn(refresh, 'requestNewAccessToken')
|
||||
// .mockImplementation((_x, _y, _z, callbackFunc) => {
|
||||
// const cb = callbackFunc as Function;
|
||||
// cb(undefined, 'ACCESS_TOKEN', undefined, {
|
||||
// expires_in: 'EXPIRES_IN',
|
||||
// id_token: 'ID_TOKEN',
|
||||
// });
|
||||
// });
|
||||
|
||||
// googleAuthProvider.refresh(mockRequest, mockResponse);
|
||||
// expect(spyRefresh).toBeCalledTimes(1);
|
||||
// expect(spyRefresh).toBeCalledWith(
|
||||
// 'google',
|
||||
// 'REFRESH_TOKEN',
|
||||
// {},
|
||||
// expect.any(Function),
|
||||
// );
|
||||
// expect(mockResponse.send).toBeCalledTimes(1);
|
||||
// expect(mockResponse.send).toBeCalledWith({
|
||||
// accessToken: 'ACCESS_TOKEN',
|
||||
// idToken: 'ID_TOKEN',
|
||||
// expiresInSeconds: 'EXPIRES_IN',
|
||||
// scope: undefined,
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('refresh token cookie and scope', () => {
|
||||
// const mockRequest = ({
|
||||
// header: () => 'XMLHttpRequest',
|
||||
// cookies: { 'google-refresh-token': 'REFRESH_TOKEN' },
|
||||
// query: {
|
||||
// scope: 'a,b',
|
||||
// },
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// const googleAuthProvider = new GoogleAuthProvider(
|
||||
// googleAuthProviderConfig,
|
||||
// );
|
||||
|
||||
// it('should fetch and return a new access token with scopes', () => {
|
||||
// const spyRefresh = jest
|
||||
// .spyOn(refresh, 'requestNewAccessToken')
|
||||
// .mockImplementation((_x, _y, _z, callbackFunc) => {
|
||||
// const cb = callbackFunc as Function;
|
||||
// cb(undefined, 'ACCESS_TOKEN', undefined, {
|
||||
// expires_in: 'EXPIRES_IN',
|
||||
// id_token: 'ID_TOKEN',
|
||||
// scope: 'a,b',
|
||||
// });
|
||||
// });
|
||||
|
||||
// googleAuthProvider.refresh(mockRequest, mockResponse);
|
||||
// expect(spyRefresh).toBeCalledTimes(1);
|
||||
// expect(spyRefresh).toBeCalledWith(
|
||||
// 'google',
|
||||
// 'REFRESH_TOKEN',
|
||||
// { scope: 'a,b' },
|
||||
// expect.any(Function),
|
||||
// );
|
||||
// expect(mockResponse.send).toBeCalledTimes(1);
|
||||
// expect(mockResponse.send).toBeCalledWith({
|
||||
// accessToken: 'ACCESS_TOKEN',
|
||||
// idToken: 'ID_TOKEN',
|
||||
// expiresInSeconds: 'EXPIRES_IN',
|
||||
// scope: 'a,b',
|
||||
// });
|
||||
// });
|
||||
|
||||
// it('ensures x-requested-with header', () => {
|
||||
// const mockHeaderRequest = ({
|
||||
// header: () => 'TEST',
|
||||
// } as unknown) as express.Request;
|
||||
|
||||
// googleAuthProvider.refresh(mockHeaderRequest, mockResponse);
|
||||
// expect(mockResponse.send).toBeCalledTimes(1);
|
||||
// expect(mockResponse.send).toBeCalledWith(
|
||||
// 'Invalid X-Requested-With header',
|
||||
// );
|
||||
// expect(mockResponse.status).toBeCalledTimes(1);
|
||||
// expect(mockResponse.status).toBeCalledWith(401);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
@@ -1,151 +1,68 @@
|
||||
// /*
|
||||
// * 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 {
|
||||
executeFrameHandlerStrategy,
|
||||
executeRedirectStrategy,
|
||||
executeRefreshTokenStrategy,
|
||||
} from '../PassportStrategyHelper';
|
||||
import {
|
||||
OAuthProviderHandlers,
|
||||
AuthInfoBase,
|
||||
AuthInfoPrivate,
|
||||
RedirectInfo,
|
||||
AuthProviderConfig,
|
||||
} from '../types';
|
||||
|
||||
// import passport from 'passport';
|
||||
// import express from 'express';
|
||||
// import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
||||
// import {
|
||||
// AuthProvider,
|
||||
// AuthProviderRouteHandlers,
|
||||
// AuthProviderConfig,
|
||||
// } from './../types';
|
||||
// import { postMessageResponse, ensuresXRequestedWith } from './../utils';
|
||||
// import { InputError } from '@backstage/backend-common';
|
||||
export class GoogleAuthProvider implements OAuthProviderHandlers {
|
||||
private readonly provider: string;
|
||||
private readonly providerConfig: AuthProviderConfig;
|
||||
private readonly _strategy: GoogleStrategy;
|
||||
|
||||
// export class GoogleAuthProvider
|
||||
// implements AuthProvider, AuthProviderRouteHandlers {
|
||||
// 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(
|
||||
{ ...this.providerConfig.options },
|
||||
(
|
||||
accessToken: any,
|
||||
refreshToken: any,
|
||||
params: any,
|
||||
profile: any,
|
||||
done: any,
|
||||
) => {
|
||||
done(
|
||||
undefined,
|
||||
{
|
||||
profile,
|
||||
idToken: params.id_token,
|
||||
accessToken,
|
||||
scope: params.scope,
|
||||
expiresInSeconds: 10,
|
||||
},
|
||||
{
|
||||
refreshToken,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// constructor(handler: OAuthProviderHandlers) {
|
||||
// this.provider = providerConfig.provider;
|
||||
// this.providerConfig = providerConfig;
|
||||
// // TODO: throw error if env variables not set?
|
||||
// this._strategy = new GoogleStrategy(
|
||||
// { ...this.providerConfig.options },
|
||||
// (
|
||||
// accessToken: any,
|
||||
// refreshToken: any,
|
||||
// params: any,
|
||||
// profile: any,
|
||||
// done: any,
|
||||
// ) => {
|
||||
// done(
|
||||
// undefined,
|
||||
// {
|
||||
// profile,
|
||||
// idToken: params.id_token,
|
||||
// accessToken,
|
||||
// scope: params.scope,
|
||||
// expiresInSeconds: params.expires_in,
|
||||
// },
|
||||
// {
|
||||
// refreshToken,
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
async start(req: express.Request, options: any): Promise<RedirectInfo> {
|
||||
return await executeRedirectStrategy(req, this._strategy, options);
|
||||
}
|
||||
|
||||
// async start(req: express.Request, res: express.Response) {
|
||||
// const scope = req.query.scope?.toString() ?? '';
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ user: AuthInfoBase; info: AuthInfoPrivate }> {
|
||||
return await executeFrameHandlerStrategy(req, this._strategy);
|
||||
}
|
||||
|
||||
// if (!scope) {
|
||||
// throw new InputError('missing scope parameter');
|
||||
// }
|
||||
|
||||
// // router -> [AuthProviderRouteHandlers] -> OAuthProvider -> [OAuthProviderHandler] -> GoogleAuthProvider
|
||||
// // router -> [AuthProviderRouteHandlers] -> GoogleAuthProvider
|
||||
|
||||
// // class GoogleAuthProvider2 implements OAuthProviderHandler {
|
||||
// // async start(req: express.Request): Promise<RedirectInfo> {
|
||||
|
||||
// // }
|
||||
// // async handler(req: express.Request): Promise<AuthInfo> {
|
||||
// // const { user, info } = await executeFrameHandlerStrategy(
|
||||
// // req,
|
||||
// // this.provider,
|
||||
// // this._strategy,
|
||||
// // );
|
||||
// // return { user, info }
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// executeRedirectStrategy(req, res, this.provider, this._strategy, {
|
||||
// scope,
|
||||
// accessType: 'offline',
|
||||
// prompt: 'consent',
|
||||
// });
|
||||
// }
|
||||
|
||||
// async frameHandler(req: express.Request, res: express.Response) {
|
||||
// try {
|
||||
// // const { user, info } = await this.handler.handler(req);
|
||||
// const { user, info } = await executeFrameHandlerStrategy(req);
|
||||
|
||||
// const { refreshToken } = info;
|
||||
// if (!refreshToken) {
|
||||
// throw new Error('Missing refresh token');
|
||||
// }
|
||||
|
||||
// setRefreshTokenCookie(res, this.provider, refreshToken);
|
||||
|
||||
// return postMessageResponse(res, {
|
||||
// type: 'auth-result',
|
||||
// payload: user,
|
||||
// });
|
||||
// } catch (error) {
|
||||
// return postMessageResponse(res, {
|
||||
// type: 'auth-result',
|
||||
// error: {
|
||||
// name: error.name,
|
||||
// message: error.message,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// async logout(req: express.Request, res: express.Response) {
|
||||
// if (!ensuresXRequestedWith(req)) {
|
||||
// return res.status(401).send('Invalid X-Requested-With header');
|
||||
// }
|
||||
|
||||
// removeRefreshTokenCookie(res, this.provider);
|
||||
// return res.send('logout!');
|
||||
// }
|
||||
|
||||
// async refresh(req: express.Request, res: express.Response) {
|
||||
// if (!ensuresXRequestedWith(req)) {
|
||||
// return res.status(401).send('Invalid X-Requested-With header');
|
||||
// }
|
||||
|
||||
// try {
|
||||
// const refreshInfo = await executeRefreshTokenStrategy(
|
||||
// req,
|
||||
// this.provider,
|
||||
// this._strategy,
|
||||
// );
|
||||
// res.send(refreshInfo);
|
||||
// } catch (error) {
|
||||
// res.status(401).send(`${error.message}`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// strategy(): passport.Strategy {
|
||||
// return this._strategy;
|
||||
// }
|
||||
// }
|
||||
async refresh(refreshToken: string, scope: string): Promise<AuthInfoBase> {
|
||||
return await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
refreshToken,
|
||||
scope,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// /*
|
||||
// * 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 passport from 'passport';
|
||||
// import express from 'express';
|
||||
// import { makeProvider, defaultRouter } from '.';
|
||||
// import { AuthProviderRouteHandlers, AuthProviderConfig } from './types';
|
||||
// import * as passportGoogleOAuth20 from 'passport-google-oauth20';
|
||||
// import { ProviderFactories } from './factories';
|
||||
|
||||
// class MyOAuthProvider implements AuthProviderHandlers {}
|
||||
|
||||
// class MyAuthProvider implements AuthProviderRouteHandlers {
|
||||
// // private readonly providerConfig: AuthProviderConfig;
|
||||
// constructor(providerConfig: AuthProviderConfig) {
|
||||
// this.providerConfig = providerConfig;
|
||||
// }
|
||||
|
||||
// strategy(): passport.Strategy {
|
||||
// return new passportGoogleOAuth20.Strategy(
|
||||
// this.providerConfig.options,
|
||||
// () => {},
|
||||
// );
|
||||
// }
|
||||
// async start(_: express.Request, res: express.Response): Promise<any> {
|
||||
// res.send('start');
|
||||
// }
|
||||
// async frameHandler(_: express.Request, res: express.Response): Promise<any> {
|
||||
// res.send('frameHandler');
|
||||
// }
|
||||
// async logout(_: express.Request, res: express.Response): Promise<any> {
|
||||
// res.send('logout');
|
||||
// }
|
||||
// }
|
||||
|
||||
// class MyAuthProviderWithRefresh extends MyAuthProvider {
|
||||
// async refresh(_: express.Request, res: express.Response): Promise<any> {
|
||||
// res.send('logout');
|
||||
// }
|
||||
// }
|
||||
|
||||
// const providerConfig = {
|
||||
// provider: 'a',
|
||||
// options: {
|
||||
// clientID: 'somevalue',
|
||||
// },
|
||||
// };
|
||||
|
||||
// const providerConfigInvalid = {
|
||||
// provider: 'b',
|
||||
// options: {
|
||||
// clientID: 'somevalue',
|
||||
// },
|
||||
// };
|
||||
|
||||
// describe('makeProvider', () => {
|
||||
// it('makes a provider for Myauthprovider', () => {
|
||||
// jest
|
||||
// .spyOn(ProviderFactories, 'getProviderFactory')
|
||||
// .mockReturnValueOnce(MyAuthProvider);
|
||||
// const provider = makeProvider(providerConfig);
|
||||
// expect(provider.providerId).toEqual('a');
|
||||
// expect(provider.providerRouter).toBeDefined();
|
||||
// });
|
||||
|
||||
// it('throws an error when provider implementation does not exist', () => {
|
||||
// expect(() => {
|
||||
// makeProvider(providerConfigInvalid);
|
||||
// }).toThrow('Provider Implementation missing for : b auth provider');
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('defaultRouter', () => {
|
||||
// it('make router for auth provider without refresh', () => {
|
||||
// expect(
|
||||
// defaultRouter(new MyAuthProvider({ provider: 'a', options: {} })),
|
||||
// ).toBeDefined();
|
||||
// });
|
||||
|
||||
// it('make router for auth provider with refresh', () => {
|
||||
// expect(
|
||||
// defaultRouter(
|
||||
// new MyAuthProviderWithRefresh({ provider: 'b', options: {} }),
|
||||
// ),
|
||||
// ).toBeDefined();
|
||||
// });
|
||||
// });
|
||||
@@ -26,34 +26,14 @@ export interface OAuthProviderHandlers {
|
||||
start(req: express.Request, options: any): Promise<any>;
|
||||
handler(req: express.Request): Promise<any>;
|
||||
refresh(refreshToken: string, scope: string): Promise<any>;
|
||||
logout(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): Promise<any>;
|
||||
logout?(): Promise<any>;
|
||||
}
|
||||
|
||||
export interface AuthProviderRouteHandlers {
|
||||
start(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): Promise<any>;
|
||||
frameHandler(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): Promise<any>;
|
||||
refresh?(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): Promise<any>;
|
||||
logout(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): Promise<any>;
|
||||
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 AuthProviderFactories = {
|
||||
|
||||
Reference in New Issue
Block a user