Skip to content

Commit

Permalink
feat: oauth support for azure and github
Browse files Browse the repository at this point in the history
  • Loading branch information
samjcombs committed Nov 19, 2024
1 parent 6bb40d0 commit d3b7f19
Show file tree
Hide file tree
Showing 16 changed files with 332 additions and 296 deletions.
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
FROM node:20-alpine AS frontend-builder
WORKDIR /app/frontend

ARG REACT_APP_BACKEND_URL
ENV REACT_APP_BACKEND_URL=$REACT_APP_BACKEND_URL
ARG REACT_APP_BACKEND_PORT
ENV REACT_APP_BACKEND_PORT=$REACT_APP_BACKEND_PORT
ARG REACT_APP_FRONTEND_URL
ENV REACT_APP_FRONTEND_URL=$REACT_APP_FRONTEND_URL
ARG REACT_APP_FRONTEND_PORT
ENV REACT_APP_FRONTEND_PORT=$REACT_APP_FRONTEND_PORT

COPY frontend/package*.json ./
RUN npm install

Expand Down
2 changes: 1 addition & 1 deletion backend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module.exports = {
ecmaVersion: 2020,
sourceType: 'module',
},
ignorePatterns: ['**/*.test.ts', 'dist/**'],
ignorePatterns: ['**/*.test.ts', 'dist/'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
Expand Down
63 changes: 16 additions & 47 deletions backend/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,25 @@ const config = convict({
format: 'port',
default: 3033,
},
apiUrl: {
env: 'API_URL',
host: {
env: 'HOST',
format: String,
default: 'http://localhost:3033',
default: '0.0.0.0',
},
websiteUrl: {
env: 'WEBSITE_URL',
backendUrl: {
env: 'BACKEND_URL',
format: String,
default: 'http://localhost:3000',
default: 'http://localhost',
},
frontendUrl: {
env: 'FRONTEND_URL',
format: String,
default: 'http://localhost',
},
frontendPort: {
env: 'FRONTEND_PORT',
format: 'port',
default: 3033,
},
},
supertokens: {
Expand All @@ -58,11 +68,6 @@ const config = convict({
sensitive: true,
default: null,
},
tenantId: {
env: 'AZURE_TENANT_ID',
format: '*',
default: null,
},
},
github: {
clientId: {
Expand All @@ -77,42 +82,6 @@ const config = convict({
default: null,
},
},
google: {
clientId: {
env: 'GOOGLE_CLIENT_ID',
format: '*',
default: null,
},
clientSecret: {
env: 'GOOGLE_CLIENT_SECRET',
format: '*',
sensitive: true,
default: null,
},
},
aws: {
clientId: {
env: 'AWS_CLIENT_ID',
format: '*',
default: null,
},
clientSecret: {
env: 'AWS_CLIENT_SECRET',
format: '*',
sensitive: true,
default: null,
},
region: {
env: 'AWS_REGION',
format: String,
default: 'us-east-1',
},
userPoolId: {
env: 'AWS_USER_POOL_ID',
format: '*',
default: null,
},
},
},
});

Expand Down
19 changes: 0 additions & 19 deletions backend/src/config/supertokens.providers.ts

This file was deleted.

181 changes: 98 additions & 83 deletions backend/src/config/supertokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,95 @@ import {TypeInput} from 'supertokens-node/types';
import config from './config';

export function getApiDomain(): string {
return config.get('server.apiUrl');
const backendUrl = config.get('server.backendUrl');
const port = config.get('server.port');
return `${backendUrl}:${port}`;
}

export function getWebsiteDomain(): string {
return config.get('server.websiteUrl');
const frontendUrl = config.get('server.frontendUrl');
const frontendPort = config.get('server.frontendPort');
return `${frontendUrl}:${frontendPort}`;
}

function getGithubProvider() {
const clientId = config.get('supertokens.github.clientId');
const clientSecret = config.get('supertokens.github.clientSecret');

if (!clientId || !clientSecret) {
return null;
}

return {
config: {
thirdPartyId: 'github',
clients: [
{
clientId,
clientSecret,
},
],
},
};
}

function getAzureProvider() {
const clientId = config.get('supertokens.azure.clientId');
const clientSecret = config.get('supertokens.azure.clientSecret');

if (!clientId || !clientSecret) {
return null;
}

return {
config: {
thirdPartyId: 'azure',
name: 'azure',
clients: [
{
clientId,
clientSecret,
scope: ['openid', 'email', 'profile', 'User.Read'],
},
],
authorizationEndpoint:
'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
tokenEndpoint:
'https://login.microsoftonline.com/common/oauth2/v2.0/token',
userInfoEndpoint: 'https://graph.microsoft.com/v1.0/me',
authorizationEndpointQueryParams: {
redirect_uri: `${getApiDomain()}/auth/callback/azure`,
},
userInfoMap: {
fromUserInfoAPI: {
userId: 'id',
email: 'mail',
},
},
},
};
}

function getConfiguredProviders() {
const providers = [];

const githubProvider = getGithubProvider();
if (githubProvider) {
providers.push(githubProvider);
}

const azureProvider = getAzureProvider();
if (azureProvider) {
providers.push(azureProvider);
}

return providers;
}

export const SuperTokensConfig: TypeInput = {
debug: true,
supertokens: {
connectionURI:
config.get('supertokens.connectionUri') || 'http://localhost:3567',
connectionURI: config.get('supertokens.connectionUri'),
},
appInfo: {
appName: 'Instant Mock',
Expand All @@ -28,88 +105,26 @@ export const SuperTokensConfig: TypeInput = {
recipeList: [
ThirdParty.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
// override the thirdparty sign in / up API
signInUp: async function (input) {
const response = await originalImplementation.signInUp(input);

if (response.status === 'OK') {
// TODO fix avatar for azure
const avatar =
response.rawUserInfoFromProvider.fromUserInfoAPI?.user
?.avatar_url;
if (avatar) {
await UserMetadata.updateUserMetadata(response.user.id, {
avatarUrl: avatar,
});
}
functions: (originalImplementation) => ({
...originalImplementation,
signInUp: async function (input) {
const response = await originalImplementation.signInUp(input);
if (response.status === 'OK') {
const avatar =
response.rawUserInfoFromProvider.fromUserInfoAPI?.user
?.avatar_url;
if (avatar) {
await UserMetadata.updateUserMetadata(response.user.id, {
avatarUrl: avatar,
});
}

return response;
},
};
},
}
return response;
},
}),
},
signInAndUpFeature: {
providers: [
{
config: {
thirdPartyId: 'github',
clients: [
{
clientId: (() => {
const clientId = config.get('supertokens.github.clientId');
if (!clientId)
throw new Error(
'GITHUB_CLIENT_ID is required for authentication'
);
return clientId;
})(),
clientSecret: (() => {
const clientSecret = config.get(
'supertokens.github.clientSecret'
);
if (!clientSecret)
throw new Error(
'GITHUB_CLIENT_SECRET is required for authentication'
);
return clientSecret;
})(),
},
],
},
},
{
config: {
thirdPartyId: 'azure',
name: 'azure',
clients: [
{
clientId: '',
clientSecret: '',
scope: ['openid', 'email', 'profile', 'User.Read'], // Include 'openid' explicitly
},
],
authorizationEndpoint:
'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
tokenEndpoint:
'https://login.microsoftonline.com/common/oauth2/v2.0/token', // Correct endpoint
userInfoEndpoint: 'https://graph.microsoft.com/v1.0/me',
authorizationEndpointQueryParams: {
redirect_uri: 'http://localhost:3000/auth/callback/azure', // Must match Azure registration
},
userInfoMap: {
fromUserInfoAPI: {
userId: 'id', // Maps to the Microsoft Graph user ID
email: 'mail', // Retrieves email
// emailVerified: 'email_verified', // Not directly available; needs a custom field
},
},
},
},
],
providers: getConfiguredProviders(),
},
}),
Session.init(),
Expand Down
9 changes: 9 additions & 0 deletions backend/src/migrations/sqlite/Migration20241119174820.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {Migration} from '@mikro-orm/migrations';

export class Migration20241119174820 extends Migration {
override async up(): Promise<void> {
this.addSql(
`alter table \`apollo_api_key\` add column \`user_id\` text not null;`
);
}
}
26 changes: 26 additions & 0 deletions backend/src/routes/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import express from 'express';
import config from '../config/config';

const router = express.Router();

router.get('/auth-providers', (_, res) => {
const providers = [];

if (config.get('supertokens.github.clientId')) {
providers.push({
id: 'github',
name: 'GitHub',
});
}

if (config.get('supertokens.azure.clientId')) {
providers.push({
id: 'azure',
name: 'Microsoft',
});
}

res.json({providers});
});

export default router;
2 changes: 2 additions & 0 deletions backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {Seed} from './models/seed';
import {SeedGroup} from './models/seedGroup';
import apolloApiKeysRoutes from './routes/apolloApiKey';
import avatarRoutes from './routes/avatar';
import authRoutes from './routes/auth';
import graphqlRoutes from './routes/graphql';
import graphsRoutes from './routes/graphs';
import proposalsRoutes from './routes/proposals';
Expand Down Expand Up @@ -107,6 +108,7 @@ const initializeApp = async () => {
})
);
app.use(authMiddleware.init);
app.use(authRoutes);
app.use('/api', authMiddleware.verify);
app.use((_, __, next) => RequestContext.create(DI.orm.em, next));
app.use('/api', seedsRoutes);
Expand Down
Loading

0 comments on commit d3b7f19

Please sign in to comment.