Merge pull request #1478 from danztran/gitlab_auth
Add Gitlab Auth #343
This commit is contained in:
@@ -27,11 +27,13 @@ import {
|
||||
GoogleAuth,
|
||||
GithubAuth,
|
||||
OktaAuth,
|
||||
GitlabAuth,
|
||||
oauthRequestApiRef,
|
||||
OAuthRequestManager,
|
||||
googleAuthApiRef,
|
||||
githubAuthApiRef,
|
||||
oktaAuthApiRef,
|
||||
gitlabAuthApiRef,
|
||||
storageApiRef,
|
||||
WebStorage,
|
||||
} from '@backstage/core';
|
||||
@@ -104,6 +106,15 @@ export const apis = (config: ConfigApi) => {
|
||||
}),
|
||||
);
|
||||
|
||||
builder.add(
|
||||
gitlabAuthApiRef,
|
||||
GitlabAuth.create({
|
||||
apiOrigin: 'http://localhost:7000',
|
||||
basePath: '/auth/',
|
||||
oauthRequestApi,
|
||||
}),
|
||||
);
|
||||
|
||||
builder.add(
|
||||
techRadarApiRef,
|
||||
new TechRadar({
|
||||
|
||||
@@ -242,11 +242,24 @@ export const githubAuthApiRef = createApiRef<
|
||||
*/
|
||||
export const oktaAuthApiRef = createApiRef<
|
||||
OAuthApi &
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionStateApi
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionStateApi
|
||||
>({
|
||||
id: 'core.auth.okta',
|
||||
description: 'Provides authentication towards Okta APIs',
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Provides authentication towards Gitlab APIs.
|
||||
*
|
||||
* See https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token
|
||||
* for a full list of supported scopes.
|
||||
*/
|
||||
export const gitlabAuthApiRef = createApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
|
||||
>({
|
||||
id: 'core.auth.gitlab',
|
||||
description: 'Provides authentication towards Gitlab APIs',
|
||||
});
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 GitlabAuth from './GitlabAuth';
|
||||
|
||||
describe('GitlabAuth', () => {
|
||||
it('should get access token', async () => {
|
||||
const getSession = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ providerInfo: { accessToken: 'access-token' } });
|
||||
const gitlabAuth = new GitlabAuth({ getSession } as any);
|
||||
|
||||
expect(await gitlabAuth.getAccessToken()).toBe('access-token');
|
||||
expect(getSession).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should normalize scope', () => {
|
||||
const tests = [
|
||||
{
|
||||
arguments: ['read_user api write_repository'],
|
||||
expect: new Set(['read_user', 'api', 'write_repository']),
|
||||
},
|
||||
{
|
||||
arguments: ['read_repository sudo'],
|
||||
expect: new Set(['read_repository', 'sudo']),
|
||||
},
|
||||
];
|
||||
|
||||
for (const test of tests) {
|
||||
expect(GitlabAuth.normalizeScope(...test.arguments)).toEqual(test.expect);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 GitlabIcon from '@material-ui/icons/AcUnit';
|
||||
import { DefaultAuthConnector } from '../../../../lib/AuthConnector';
|
||||
import { GitlabSession } from './types';
|
||||
import {
|
||||
OAuthApi,
|
||||
SessionStateApi,
|
||||
SessionState,
|
||||
ProfileInfo,
|
||||
BackstageIdentity,
|
||||
AuthRequestOptions,
|
||||
} from '../../../definitions/auth';
|
||||
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
|
||||
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
|
||||
import { StaticAuthSessionManager } from '../../../../lib/AuthSessionManager';
|
||||
import { Observable } from '../../../../types';
|
||||
|
||||
type CreateOptions = {
|
||||
apiOrigin: string;
|
||||
basePath: string;
|
||||
|
||||
oauthRequestApi: OAuthRequestApi;
|
||||
|
||||
environment?: string;
|
||||
provider?: AuthProvider & { id: string };
|
||||
};
|
||||
|
||||
export type GitlabAuthResponse = {
|
||||
providerInfo: {
|
||||
accessToken: string;
|
||||
scope: string;
|
||||
expiresInSeconds: number;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
|
||||
const DEFAULT_PROVIDER = {
|
||||
id: 'gitlab',
|
||||
title: 'Gitlab',
|
||||
icon: GitlabIcon,
|
||||
};
|
||||
|
||||
class GitlabAuth implements OAuthApi, SessionStateApi {
|
||||
static create({
|
||||
apiOrigin,
|
||||
basePath,
|
||||
environment = 'development',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
}: CreateOptions) {
|
||||
const connector = new DefaultAuthConnector({
|
||||
apiOrigin,
|
||||
basePath,
|
||||
environment,
|
||||
provider,
|
||||
oauthRequestApi,
|
||||
sessionTransform(res: GitlabAuthResponse): GitlabSession {
|
||||
return {
|
||||
...res,
|
||||
providerInfo: {
|
||||
accessToken: res.providerInfo.accessToken,
|
||||
scopes: GitlabAuth.normalizeScope(res.providerInfo.scope),
|
||||
expiresAt: new Date(
|
||||
Date.now() + res.providerInfo.expiresInSeconds * 1000,
|
||||
),
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const sessionManager = new StaticAuthSessionManager({
|
||||
connector,
|
||||
defaultScopes: new Set(['read_user']),
|
||||
sessionScopes: (session: GitlabSession) => session.providerInfo.scopes,
|
||||
});
|
||||
|
||||
return new GitlabAuth(sessionManager);
|
||||
}
|
||||
|
||||
sessionState$(): Observable<SessionState> {
|
||||
return this.sessionManager.sessionState$();
|
||||
}
|
||||
|
||||
constructor(private readonly sessionManager: SessionManager<GitlabSession>) {}
|
||||
|
||||
async getAccessToken(scope?: string, options?: AuthRequestOptions) {
|
||||
const session = await this.sessionManager.getSession({
|
||||
...options,
|
||||
scopes: GitlabAuth.normalizeScope(scope),
|
||||
});
|
||||
return session?.providerInfo.accessToken ?? '';
|
||||
}
|
||||
|
||||
async getBackstageIdentity(
|
||||
options: AuthRequestOptions = {},
|
||||
): Promise<BackstageIdentity | undefined> {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
return session?.backstageIdentity;
|
||||
}
|
||||
|
||||
async getProfile(options: AuthRequestOptions = {}) {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
return session?.profile;
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await this.sessionManager.removeSession();
|
||||
}
|
||||
|
||||
static normalizeScope(scope?: string): Set<string> {
|
||||
if (!scope) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
const scopeList = Array.isArray(scope) ? scope : scope.split(' ');
|
||||
return new Set(scopeList);
|
||||
}
|
||||
}
|
||||
export default GitlabAuth;
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 * from './types';
|
||||
export { default as GitlabAuth } from './GitlabAuth';
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { ProfileInfo, BackstageIdentity } from '../../../definitions';
|
||||
|
||||
export type GitlabSession = {
|
||||
providerInfo: {
|
||||
accessToken: string;
|
||||
scopes: Set<string>;
|
||||
expiresAt: Date;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
@@ -16,4 +16,5 @@
|
||||
|
||||
export * from './google';
|
||||
export * from './github';
|
||||
export * from './gitlab';
|
||||
export * from './okta';
|
||||
|
||||
@@ -22,6 +22,7 @@ import { SidebarContext } from './config';
|
||||
import {
|
||||
googleAuthApiRef,
|
||||
githubAuthApiRef,
|
||||
gitlabAuthApiRef,
|
||||
identityApiRef,
|
||||
oktaAuthApiRef,
|
||||
useApi,
|
||||
@@ -57,6 +58,11 @@ export function SidebarUserSettings() {
|
||||
apiRef={githubAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
<OAuthProviderSettings
|
||||
title="Gitlab"
|
||||
apiRef={gitlabAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
<OIDCProviderSettings
|
||||
title="Okta"
|
||||
apiRef={oktaAuthApiRef}
|
||||
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
googleAuthApiRef,
|
||||
GithubAuth,
|
||||
githubAuthApiRef,
|
||||
GitlabAuth,
|
||||
gitlabAuthApiRef,
|
||||
} from '@backstage/core';
|
||||
|
||||
// TODO(rugvip): We should likely figure out how to reuse all of these between apps
|
||||
@@ -75,3 +77,14 @@ export const githubAuthApiFactory = createApiFactory({
|
||||
oauthRequestApi,
|
||||
}),
|
||||
});
|
||||
|
||||
export const gitlabAuthApiFactory = createApiFactory({
|
||||
implements: gitlabAuthApiRef,
|
||||
deps: { oauthRequestApi: oauthRequestApiRef },
|
||||
factory: ({ oauthRequestApi }) =>
|
||||
GitlabAuth.create({
|
||||
apiOrigin: 'http://localhost:7000',
|
||||
basePath: '/auth/',
|
||||
oauthRequestApi,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -6,12 +6,14 @@ import {
|
||||
OAuthRequestManager,
|
||||
googleAuthApiRef,
|
||||
githubAuthApiRef,
|
||||
gitlabAuthApiRef,
|
||||
oktaAuthApiRef,
|
||||
AlertApiForwarder,
|
||||
ErrorApiForwarder,
|
||||
ErrorAlerter,
|
||||
GoogleAuth,
|
||||
GithubAuth,
|
||||
GitlabAuth,
|
||||
OktaAuth,
|
||||
identityApiRef,
|
||||
} from '@backstage/core';
|
||||
@@ -52,6 +54,15 @@ builder.add(
|
||||
}),
|
||||
);
|
||||
|
||||
builder.add(
|
||||
gitlabAuthApiRef,
|
||||
GitlabAuth.create({
|
||||
apiOrigin: 'http://localhost:7000',
|
||||
basePath: '/auth/',
|
||||
oauthRequestApi,
|
||||
}),
|
||||
);
|
||||
|
||||
builder.add(
|
||||
oktaAuthApiRef,
|
||||
OktaAuth.create({
|
||||
|
||||
@@ -7,17 +7,46 @@ This is the backend part of the auth plugin.
|
||||
It responds to auth requests from the frontend, and fulfills them by delegating
|
||||
to the appropriate provider in the backend.
|
||||
|
||||
## Requirements
|
||||
|
||||
Needs AUTH_GOOGLE_CLIENT_ID and AUTH_GOOGLE_CLIENT_SECRET set in the environment for the backend to startup
|
||||
|
||||
## Local development
|
||||
|
||||
export AUTH_GOOGLE_CLIENT_ID=<INSERT_CLIENT_ID_HERE>
|
||||
read -r AUTH_GOOGLE_CLIENT_SECRET
|
||||
<COPY_PASTE_CLIENT_SECRET_HERE>
|
||||
export AUTH_GOOGLE_CLIENT_SECRET
|
||||
run `yarn start` in packages/backend folder
|
||||
Choose your OAuth Providers, replace `x` with actual value and then start backend:
|
||||
Example for Google Oauth Provider at root directory:
|
||||
|
||||
```bash
|
||||
export AUTH_GOOGLE_CLIENT_ID=x
|
||||
export AUTH_GOOGLE_CLIENT_SECRET=x
|
||||
yarn --cwd packages/backend start
|
||||
```
|
||||
|
||||
### Google
|
||||
|
||||
```bash
|
||||
export AUTH_GOOGLE_CLIENT_ID=x
|
||||
export AUTH_GOOGLE_CLIENT_SECRET=x
|
||||
```
|
||||
|
||||
### Github
|
||||
|
||||
```bash
|
||||
export AUTH_GITHUB_CLIENT_ID=x
|
||||
export AUTH_GITHUB_CLIENT_SECRET=x
|
||||
```
|
||||
|
||||
### Gitlab
|
||||
|
||||
```bash
|
||||
export GITLAB_BASE_URL=x # default is https://gitlab.com
|
||||
export AUTH_GITLAB_CLIENT_ID=x
|
||||
export AUTH_GITLAB_CLIENT_SECRET=x
|
||||
```
|
||||
|
||||
### Okta
|
||||
|
||||
```bash
|
||||
export AUTH_OKTA_AUDIENCE=x
|
||||
export AUTH_OKTA_CLIENT_ID=x
|
||||
export AUTH_OKTA_CLIENT_SECRET=x
|
||||
```
|
||||
|
||||
### SAML
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"morgan": "^1.10.0",
|
||||
"passport": "^0.4.1",
|
||||
"passport-github2": "^0.1.12",
|
||||
"passport-gitlab2": "^5.0.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-oauth2": "^1.5.0",
|
||||
"passport-okta-oauth": "^0.0.1",
|
||||
|
||||
@@ -137,7 +137,7 @@ export const executeRefreshTokenStrategy = async (
|
||||
params: any,
|
||||
) => {
|
||||
if (err) {
|
||||
reject(new Error(`Failed to refresh access token ${err}`));
|
||||
reject(new Error(`Failed to refresh access token ${err.toString()}`));
|
||||
}
|
||||
if (!accessToken) {
|
||||
reject(
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import Router from 'express-promise-router';
|
||||
import { createGithubProvider } from './github';
|
||||
import { createGoogleProvider } from './google';
|
||||
import { createGitlabProvider } from './gitlab';
|
||||
import { createSamlProvider } from './saml';
|
||||
import { createOktaProvider } from './okta';
|
||||
import { AuthProviderFactory, AuthProviderConfig } from './types';
|
||||
@@ -26,6 +27,7 @@ import { TokenIssuer } from '../identity';
|
||||
const factories: { [providerId: string]: AuthProviderFactory } = {
|
||||
google: createGoogleProvider,
|
||||
github: createGithubProvider,
|
||||
gitlab: createGitlabProvider,
|
||||
saml: createSamlProvider,
|
||||
okta: createOktaProvider,
|
||||
};
|
||||
|
||||
@@ -0,0 +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 { createGitlabProvider } from './provider';
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 { GitlabAuthProvider } from './provider';
|
||||
|
||||
describe('GitlabAuthProvider', () => {
|
||||
it('should transform to type OAuthResponse', () => {
|
||||
const tests = [
|
||||
{
|
||||
arguments: {
|
||||
accessToken: '19xasczxcm9n7gacn9jdgm19me',
|
||||
rawProfile: {
|
||||
id: 'uid-123',
|
||||
username: 'jimmymarkum',
|
||||
provider: 'gitlab',
|
||||
displayName: 'Jimmy Markum',
|
||||
emails: [
|
||||
{
|
||||
value: 'jimmymarkum@gmail.com',
|
||||
},
|
||||
],
|
||||
avatarUrl:
|
||||
'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg',
|
||||
},
|
||||
params: {
|
||||
scope: 'user_read write_repository',
|
||||
expires_in: 100,
|
||||
},
|
||||
},
|
||||
expect: {
|
||||
providerInfo: {
|
||||
accessToken: '19xasczxcm9n7gacn9jdgm19me',
|
||||
expiresInSeconds: 100,
|
||||
scope: 'user_read write_repository',
|
||||
},
|
||||
profile: {
|
||||
email: 'jimmymarkum@gmail.com',
|
||||
displayName: 'Jimmy Markum',
|
||||
picture:
|
||||
'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
accessToken:
|
||||
'ajakljsdoiahoawxbrouawucmbawe.awkxjemaneasdxwe.sodijxqeqwexeqwxe',
|
||||
rawProfile: {
|
||||
id: 'ipd12039',
|
||||
username: 'daveboyle',
|
||||
provider: 'gitlab',
|
||||
displayName: 'Dave Boyle',
|
||||
emails: [
|
||||
{
|
||||
value: 'daveboyle@gitlab.org',
|
||||
},
|
||||
],
|
||||
},
|
||||
params: {
|
||||
scope: 'read_repository',
|
||||
},
|
||||
},
|
||||
expect: {
|
||||
providerInfo: {
|
||||
accessToken:
|
||||
'ajakljsdoiahoawxbrouawucmbawe.awkxjemaneasdxwe.sodijxqeqwexeqwxe',
|
||||
scope: 'read_repository',
|
||||
},
|
||||
profile: {
|
||||
displayName: 'Dave Boyle',
|
||||
email: 'daveboyle@gitlab.org',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const test of tests) {
|
||||
expect(
|
||||
GitlabAuthProvider.transformOAuthResponse(
|
||||
test.arguments.accessToken,
|
||||
test.arguments.rawProfile,
|
||||
test.arguments.params,
|
||||
),
|
||||
).toEqual(test.expect);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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 GitlabStrategy } from 'passport-gitlab2';
|
||||
import {
|
||||
executeFrameHandlerStrategy,
|
||||
executeRedirectStrategy,
|
||||
makeProfileInfo,
|
||||
} from '../../lib/PassportStrategyHelper';
|
||||
import {
|
||||
OAuthProviderHandlers,
|
||||
AuthProviderConfig,
|
||||
RedirectInfo,
|
||||
EnvironmentProviderConfig,
|
||||
OAuthProviderOptions,
|
||||
OAuthProviderConfig,
|
||||
OAuthResponse,
|
||||
PassportDoneCallback,
|
||||
} from '../types';
|
||||
import { OAuthProvider } from '../../lib/OAuthProvider';
|
||||
import {
|
||||
EnvironmentHandlers,
|
||||
EnvironmentHandler,
|
||||
} from '../../lib/EnvironmentHandler';
|
||||
import { Logger } from 'winston';
|
||||
import { TokenIssuer } from '../../identity';
|
||||
import passport from 'passport';
|
||||
|
||||
export class GitlabAuthProvider implements OAuthProviderHandlers {
|
||||
private readonly _strategy: GitlabStrategy;
|
||||
|
||||
static transformPassportProfile(rawProfile: any): passport.Profile {
|
||||
const profile: passport.Profile = {
|
||||
id: rawProfile.id,
|
||||
username: rawProfile.username,
|
||||
provider: rawProfile.provider,
|
||||
displayName: rawProfile.displayName,
|
||||
};
|
||||
|
||||
if (rawProfile.emails && rawProfile.emails.length > 0) {
|
||||
profile.emails = rawProfile.emails;
|
||||
}
|
||||
if (rawProfile.avatarUrl) {
|
||||
profile.photos = [{ value: rawProfile.avatarUrl }];
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
static transformOAuthResponse(
|
||||
accessToken: string,
|
||||
rawProfile: any,
|
||||
params: any = {},
|
||||
): OAuthResponse {
|
||||
const passportProfile = GitlabAuthProvider.transformPassportProfile(
|
||||
rawProfile,
|
||||
);
|
||||
|
||||
const profile = makeProfileInfo(passportProfile, params.id_token);
|
||||
const providerInfo = {
|
||||
accessToken,
|
||||
scope: params.scope,
|
||||
expiresInSeconds: params.expires_in,
|
||||
idToken: params.id_token,
|
||||
};
|
||||
|
||||
if (params.expires_in) {
|
||||
providerInfo.expiresInSeconds = params.expires_in;
|
||||
}
|
||||
if (params.id_token) {
|
||||
providerInfo.idToken = params.id_token;
|
||||
}
|
||||
return {
|
||||
providerInfo,
|
||||
profile,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(options: OAuthProviderOptions) {
|
||||
this._strategy = new GitlabStrategy(
|
||||
{ ...options },
|
||||
(
|
||||
accessToken: any,
|
||||
_: any,
|
||||
params: any,
|
||||
rawProfile: any,
|
||||
done: PassportDoneCallback<OAuthResponse>,
|
||||
) => {
|
||||
const oauthResponse = GitlabAuthProvider.transformOAuthResponse(
|
||||
accessToken,
|
||||
rawProfile,
|
||||
params,
|
||||
);
|
||||
done(undefined, oauthResponse);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async start(
|
||||
req: express.Request,
|
||||
options: Record<string, string>,
|
||||
): Promise<RedirectInfo> {
|
||||
return await executeRedirectStrategy(req, this._strategy, options);
|
||||
}
|
||||
|
||||
async handler(req: express.Request): Promise<{ response: OAuthResponse }> {
|
||||
return await executeFrameHandlerStrategy<OAuthResponse>(
|
||||
req,
|
||||
this._strategy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function createGitlabProvider(
|
||||
{ baseUrl }: AuthProviderConfig,
|
||||
providerConfig: EnvironmentProviderConfig,
|
||||
logger: Logger,
|
||||
tokenIssuer: TokenIssuer,
|
||||
) {
|
||||
const envProviders: EnvironmentHandlers = {};
|
||||
|
||||
for (const [env, envConfig] of Object.entries(providerConfig)) {
|
||||
const {
|
||||
secure,
|
||||
appOrigin,
|
||||
clientId,
|
||||
clientSecret,
|
||||
audience,
|
||||
} = (envConfig as unknown) as OAuthProviderConfig;
|
||||
const callbackURLParam = `?env=${env}`;
|
||||
|
||||
const opts = {
|
||||
clientID: clientId,
|
||||
clientSecret: clientSecret,
|
||||
callbackURL: `${baseUrl}/gitlab/handler/frame${callbackURLParam}`,
|
||||
baseURL: audience,
|
||||
};
|
||||
|
||||
if (!opts.clientID || !opts.clientSecret) {
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
'Failed to initialize Gitlab auth provider, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars',
|
||||
);
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
'Gitlab auth provider disabled, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars to enable',
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
envProviders[env] = new OAuthProvider(new GitlabAuthProvider(opts), {
|
||||
disableRefresh: true,
|
||||
providerId: 'gitlab',
|
||||
secure,
|
||||
baseUrl,
|
||||
appOrigin,
|
||||
tokenIssuer,
|
||||
});
|
||||
}
|
||||
return new EnvironmentHandler(envProviders);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare module 'passport-gitlab2' {
|
||||
import { Request } from 'express';
|
||||
import { StrategyCreated } from 'passport';
|
||||
|
||||
export class Strategy {
|
||||
constructor(options: any, verify: any);
|
||||
authenticate(this: StrategyCreated<this>, req: Request, options?: any): any;
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,15 @@ export async function createRouter(
|
||||
clientSecret: process.env.AUTH_GITHUB_CLIENT_SECRET!,
|
||||
},
|
||||
},
|
||||
gitlab: {
|
||||
development: {
|
||||
appOrigin: 'http://localhost:3000',
|
||||
secure: false,
|
||||
clientId: process.env.AUTH_GITLAB_CLIENT_ID!,
|
||||
clientSecret: process.env.AUTH_GITLAB_CLIENT_SECRET!,
|
||||
audience: process.env.GITLAB_BASE_URL! || 'https://gitlab.com',
|
||||
},
|
||||
},
|
||||
saml: {
|
||||
development: {
|
||||
entryPoint: 'http://localhost:7001/',
|
||||
@@ -89,8 +98,8 @@ export async function createRouter(
|
||||
clientId: process.env.AUTH_OKTA_CLIENT_ID!,
|
||||
clientSecret: process.env.AUTH_OKTA_CLIENT_SECRET!,
|
||||
audience: process.env.AUTH_OKTA_AUDIENCE,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13986,6 +13986,13 @@ passport-github2@^0.1.12:
|
||||
dependencies:
|
||||
passport-oauth2 "1.x.x"
|
||||
|
||||
passport-gitlab2@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/passport-gitlab2/-/passport-gitlab2-5.0.0.tgz#ea37e5285321c026a02671e87469cac28cce9b69"
|
||||
integrity sha512-cXQMgM6JQx9wHVh7JLH30D8fplfwjsDwRz+zS0pqC8JS+4bNmc1J04NGp5g2M4yfwylH9kQRrMN98GxMw7q7cg==
|
||||
dependencies:
|
||||
passport-oauth2 "^1.4.0"
|
||||
|
||||
passport-google-oauth20@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef"
|
||||
@@ -14002,7 +14009,7 @@ passport-oauth1@1.x.x:
|
||||
passport-strategy "1.x.x"
|
||||
utils-merge "1.x.x"
|
||||
|
||||
passport-oauth2@1.x.x, passport-oauth2@^1.5.0:
|
||||
passport-oauth2@1.x.x, passport-oauth2@^1.4.0, passport-oauth2@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108"
|
||||
integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==
|
||||
|
||||
Reference in New Issue
Block a user