108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
import {Controller, Get} from '@overnightjs/core';
|
|
import type {NextFunction, Request, Response} from 'express';
|
|
|
|
import {
|
|
API_URI,
|
|
DASHBOARD_URI,
|
|
GITHUB_OAUTH_CLIENT,
|
|
GITHUB_OAUTH_ENABLED,
|
|
GITHUB_OAUTH_SECRET,
|
|
} from '../../app/constants.js';
|
|
import {prisma} from '../../database/prisma.js';
|
|
import {jwt} from '../../middleware/auth.js';
|
|
import {NtfyService} from '../../services/NtfyService.js';
|
|
import {UserService} from '../../services/UserService.js';
|
|
import {CatchAsync} from '../../utils/asyncHandler.js';
|
|
|
|
@Controller('github')
|
|
export class Github {
|
|
@Get('outbound')
|
|
public sendToOutbound(req: Request, res: Response) {
|
|
if (!GITHUB_OAUTH_ENABLED) {
|
|
return res.status(404).json({error: 'GitHub OAuth is not configured'});
|
|
}
|
|
|
|
const OAUTH_QS = new URLSearchParams({
|
|
client_id: GITHUB_OAUTH_CLIENT,
|
|
redirect_uri: `${API_URI}/oauth/github/callback`,
|
|
response_type: 'code',
|
|
scope: 'user:email',
|
|
});
|
|
|
|
return res.redirect(`https://github.com/login/oauth/authorize?${OAUTH_QS.toString()}`);
|
|
}
|
|
|
|
@Get('callback')
|
|
@CatchAsync
|
|
public async callback(req: Request, res: Response, _next: NextFunction) {
|
|
if (!GITHUB_OAUTH_ENABLED) {
|
|
return res.status(404).json({error: 'GitHub OAuth is not configured'});
|
|
}
|
|
const {code} = req.query;
|
|
|
|
if (!code || typeof code !== 'string') {
|
|
return res.redirect(DASHBOARD_URI + '/auth/login?message=Invalid OAuth callback');
|
|
}
|
|
|
|
const data = new URLSearchParams({
|
|
client_id: GITHUB_OAUTH_CLIENT,
|
|
client_secret: GITHUB_OAUTH_SECRET,
|
|
code: code as string,
|
|
redirect_uri: `${API_URI}/oauth/github/callback`,
|
|
});
|
|
|
|
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
|
|
method: 'POST',
|
|
headers: {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'},
|
|
body: data,
|
|
}).then(res => res.json());
|
|
|
|
if (!tokenResponse.access_token || !tokenResponse.token_type) {
|
|
return res.redirect(DASHBOARD_URI + '/auth/login?message=Failed to authenticate with GitHub');
|
|
}
|
|
|
|
const emails = await fetch(`https://api.github.com/user/emails`, {
|
|
headers: {Authorization: `${tokenResponse.token_type} ${tokenResponse.access_token}`},
|
|
}).then(res => res.json());
|
|
|
|
if (!Array.isArray(emails) || emails.length === 0) {
|
|
return res.redirect(DASHBOARD_URI + '/auth/login?message=Failed to retrieve emails from GitHub');
|
|
}
|
|
|
|
const primaryEmail = emails.find((e: {primary: boolean; email: string}) => e.primary);
|
|
|
|
if (!primaryEmail || !primaryEmail.email || typeof primaryEmail.email !== 'string') {
|
|
return res.redirect(DASHBOARD_URI + '/auth/login?message=Failed to retrieve primary email from GitHub');
|
|
}
|
|
|
|
const email = primaryEmail.email;
|
|
|
|
let user = await UserService.email(email);
|
|
let isNewUser = false;
|
|
|
|
if (!user) {
|
|
user = await prisma.user.create({
|
|
data: {
|
|
email,
|
|
type: 'GITHUB_OAUTH',
|
|
},
|
|
});
|
|
isNewUser = true;
|
|
}
|
|
|
|
if (user.type !== 'GITHUB_OAUTH') {
|
|
return res.redirect(DASHBOARD_URI + '/auth/login?message=You used another form of authentication');
|
|
}
|
|
|
|
// Send notification if this is a new user
|
|
if (isNewUser) {
|
|
await NtfyService.notifyUserOAuthSignup(user.email, user.id, 'GitHub');
|
|
}
|
|
|
|
const token = jwt.sign(user.id);
|
|
const cookie = UserService.cookieOptions();
|
|
|
|
res.cookie(UserService.COOKIE_NAME, token, cookie).redirect(DASHBOARD_URI);
|
|
}
|
|
}
|