feat: oauth sign up
This commit is contained in:
parent
0f641aa852
commit
b0c3c6f45a
19 changed files with 397 additions and 48 deletions
|
@ -17,6 +17,8 @@ module.exports = {
|
|||
'getsharex.com',
|
||||
// For flameshot icon, and maybe in the future other stuff from github
|
||||
'raw.githubusercontent.com',
|
||||
// Discord Icon
|
||||
'assets-global.website-files.com',
|
||||
],
|
||||
},
|
||||
poweredByHeader: false,
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "oauth" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "oauthProvider" TEXT,
|
||||
ALTER COLUMN "password" DROP NOT NULL;
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "oauthAccessToken" TEXT;
|
|
@ -8,21 +8,24 @@ generator client {
|
|||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String
|
||||
password String
|
||||
avatar String?
|
||||
token String
|
||||
administrator Boolean @default(false)
|
||||
systemTheme String @default("system")
|
||||
embedTitle String?
|
||||
embedColor String @default("#2f3136")
|
||||
embedSiteName String? @default("{image.file} • {user.name}")
|
||||
ratelimit DateTime?
|
||||
domains String[]
|
||||
images Image[]
|
||||
urls Url[]
|
||||
Invite Invite[]
|
||||
id Int @id @default(autoincrement())
|
||||
username String
|
||||
password String?
|
||||
oauth Boolean @default(false)
|
||||
oauthProvider String?
|
||||
oauthAccessToken String?
|
||||
avatar String?
|
||||
token String
|
||||
administrator Boolean @default(false)
|
||||
systemTheme String @default("system")
|
||||
embedTitle String?
|
||||
embedColor String @default("#2f3136")
|
||||
embedSiteName String? @default("{image.file} • {user.name}")
|
||||
ratelimit DateTime?
|
||||
domains String[]
|
||||
images Image[]
|
||||
urls Url[]
|
||||
Invite Invite[]
|
||||
}
|
||||
|
||||
enum ImageFormat {
|
||||
|
|
7
src/components/icons/DiscordIcon.tsx
Normal file
7
src/components/icons/DiscordIcon.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
// https://discord.com/branding
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function DiscordIcon({ ...props }) {
|
||||
return <Image src='https://assets-global.website-files.com/6257adef93867e50d84d30e2/62595384f934b806f37f4956_145dc557845548a36a82337912ca3ac5.svg' width={24} height={24} {...props} />;
|
||||
}
|
5
src/components/icons/GitHubIcon.tsx
Normal file
5
src/components/icons/GitHubIcon.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { GitHub } from 'react-feather';
|
||||
|
||||
export default function GitHubIcon({ ...props }) {
|
||||
return <GitHub size={24} {...props} />;
|
||||
}
|
|
@ -99,6 +99,15 @@ export interface ConfigDiscordEmbed {
|
|||
|
||||
export interface ConfigFeatures {
|
||||
invites: boolean;
|
||||
oauth_registration: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigOAuth {
|
||||
github_client_id?: string;
|
||||
github_client_secret?: string;
|
||||
|
||||
discord_client_id?: string;
|
||||
discord_client_secret?: string;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
|
@ -109,5 +118,6 @@ export interface Config {
|
|||
datasource: ConfigDatasource;
|
||||
website: ConfigWebsite;
|
||||
discord: ConfigDiscord;
|
||||
oauth: ConfigOAuth;
|
||||
features: ConfigFeatures;
|
||||
}
|
|
@ -110,8 +110,15 @@ export default function readConfig() {
|
|||
map('DISCORD_SHORTEN_EMBED_IMAGE', 'boolean', 'discord.shorten.embed.image'),
|
||||
map('DISCORD_SHORTEN_EMBED_THUMBNAIL', 'boolean', 'discord.shorten.embed.thumbnail'),
|
||||
map('DISCORD_SHORTEN_EMBED_TIMESTAMP', 'boolean', 'discord.shorten.embed.timestamp'),
|
||||
|
||||
|
||||
map('OAUTH_GITHUB_CLIENT_ID', 'string', 'oauth.github_client_id'),
|
||||
map('OAUTH_GITHUB_CLIENT_SECRET', 'string', 'oauth.github_client_secret'),
|
||||
|
||||
map('OAUTH_DISCORD_CLIENT_ID', 'string', 'oauth.discord_client_id'),
|
||||
map('OAUTH_DISCORD_CLIENT_SECRET', 'string', 'oauth.discord_client_secret'),
|
||||
|
||||
map('FEATURES_INVITES', 'boolean', 'features.invites'),
|
||||
map('FEATURES_OAUTH_REGISTRATION', 'boolean', 'features.oauth_registration'),
|
||||
];
|
||||
|
||||
const config = {};
|
||||
|
|
|
@ -86,9 +86,18 @@ const validator = object({
|
|||
upload: discord_content,
|
||||
shorten: discord_content,
|
||||
}).optional().nullable().default(null),
|
||||
oauth: object({
|
||||
github_client_id: string().nullable().default(null),
|
||||
github_client_secret: string().nullable().default(null),
|
||||
|
||||
discord_client_id: string().nullable().default(null),
|
||||
discord_client_secret: string().nullable().default(null),
|
||||
}).optional().nullable().default(null),
|
||||
features: object({
|
||||
invites: boolean().default(true),
|
||||
oauth_registration: boolean().default(false),
|
||||
}).required(),
|
||||
|
||||
});
|
||||
|
||||
export default function validate(config): Config {
|
||||
|
@ -125,6 +134,7 @@ export default function validate(config): Config {
|
|||
}
|
||||
}
|
||||
|
||||
console.log(validated);
|
||||
return validated as unknown as Config;
|
||||
} catch (e) {
|
||||
if (process.env.ZIPLINE_DOCKER_BUILD) return null;
|
||||
|
|
|
@ -13,7 +13,11 @@ export default function login() {
|
|||
setLoading(true);
|
||||
|
||||
const res = await useFetch('/api/user');
|
||||
if (res.error) return router.push('/auth/login?url=' + router.route);
|
||||
if (res.error) {
|
||||
if (res.error === 'oauth token expired') return router.push(res.redirect_uri);
|
||||
|
||||
return router.push('/auth/login?url=' + router.route);
|
||||
}
|
||||
|
||||
setUser(res);
|
||||
setLoading(false);
|
||||
|
|
|
@ -1,13 +1,32 @@
|
|||
import config from 'lib/config';
|
||||
import { discord_auth, github_auth } from 'lib/oauth';
|
||||
import { notNull } from 'lib/util';
|
||||
import { GetServerSideProps } from 'next';
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async () => {
|
||||
export const getServerSideProps: GetServerSideProps = async ctx => {
|
||||
// this entire thing will also probably change before the stable release
|
||||
const ghEnabled = notNull(config.oauth.github_client_id, config.oauth.github_client_secret);
|
||||
const discEnabled = notNull(config.oauth.discord_client_id, config.oauth.discord_client_secret);
|
||||
|
||||
const oauth_providers = [];
|
||||
|
||||
if (ghEnabled) oauth_providers.push({
|
||||
name: 'GitHub',
|
||||
url: github_auth.oauth_url(config.oauth.github_client_id),
|
||||
});
|
||||
if (discEnabled) oauth_providers.push({
|
||||
name: 'Discord',
|
||||
url: discord_auth.oauth_url(config.oauth.discord_client_id, `${config.core.https ? 'https' : 'http'}://${ctx.req.headers.host}`),
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
title: config.website.title,
|
||||
external_links: JSON.stringify(config.website.external_links),
|
||||
disable_media_preview: config.website.disable_media_preview,
|
||||
invites: config.features.invites,
|
||||
oauth_registration: config.features.oauth_registration,
|
||||
oauth_providers: JSON.stringify(oauth_providers),
|
||||
},
|
||||
};
|
||||
};
|
|
@ -5,6 +5,7 @@ import { serialize } from 'cookie';
|
|||
import { sign64, unsign64 } from 'lib/util';
|
||||
import config from 'lib/config';
|
||||
import prisma from 'lib/prisma';
|
||||
import { User } from '@prisma/client';
|
||||
|
||||
export interface NextApiFile {
|
||||
fieldname: string;
|
||||
|
@ -16,18 +17,7 @@ export interface NextApiFile {
|
|||
}
|
||||
|
||||
export type NextApiReq = NextApiRequest & {
|
||||
user: () => Promise<{
|
||||
username: string;
|
||||
token: string;
|
||||
embedTitle: string;
|
||||
embedColor: string;
|
||||
systemTheme: string;
|
||||
administrator: boolean;
|
||||
id: number;
|
||||
password: string;
|
||||
domains: string[];
|
||||
avatar?: string;
|
||||
} | null | void>;
|
||||
user: () => Promise<User | null | void>;
|
||||
getCookie: (name: string) => string | null;
|
||||
cleanCookie: (name: string) => void;
|
||||
files?: NextApiFile[];
|
||||
|
@ -100,23 +90,11 @@ export const withZipline = (handler: (req: NextApiRequest, res: NextApiResponse)
|
|||
try {
|
||||
const userId = req.getCookie('user');
|
||||
if (!userId) return null;
|
||||
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: Number(userId),
|
||||
},
|
||||
select: {
|
||||
administrator: true,
|
||||
embedColor: true,
|
||||
embedTitle: true,
|
||||
id: true,
|
||||
password: true,
|
||||
systemTheme: true,
|
||||
token: true,
|
||||
username: true,
|
||||
domains: true,
|
||||
avatar: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) return null;
|
||||
|
@ -140,7 +118,7 @@ export const setCookie = (
|
|||
value: unknown,
|
||||
options: CookieSerializeOptions = {}
|
||||
) => {
|
||||
|
||||
|
||||
if ('maxAge' in options) {
|
||||
options.expires = new Date(Date.now() + options.maxAge * 1000);
|
||||
options.maxAge /= 1000;
|
||||
|
|
27
src/lib/oauth/index.ts
Normal file
27
src/lib/oauth/index.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
export const github_auth = {
|
||||
oauth_url: (clientId: string) => `https://github.com/login/oauth/authorize?client_id=${clientId}&scope=user`,
|
||||
oauth_user: async (access_token: string) => {
|
||||
const res = await fetch('https://api.github.com/user', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`,
|
||||
},
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
|
||||
return res.json();
|
||||
},
|
||||
};
|
||||
|
||||
export const discord_auth = {
|
||||
oauth_url: (clientId: string, origin: string) => `https://discord.com/api/oauth2/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(`${origin}/api/auth/oauth/discord`)}&response_type=code&scope=identify`,
|
||||
oauth_user: async (access_token: string) => {
|
||||
const res = await fetch('https://discord.com/api/users/@me', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${access_token}`,
|
||||
},
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
|
||||
return res.json();
|
||||
},
|
||||
};
|
|
@ -144,4 +144,18 @@ export function createInvisURL(length: number, urlId: string) {
|
|||
};
|
||||
|
||||
return retry();
|
||||
}
|
||||
|
||||
export async function getBase64URLFromURL(url: string) {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) return null;
|
||||
|
||||
const buffer = await res.arrayBuffer();
|
||||
const base64 = Buffer.from(buffer).toString('base64');
|
||||
|
||||
return `data:${res.headers.get('content-type')};base64,${base64}`;
|
||||
}
|
||||
|
||||
export async function notNull(a: any, b: any) {
|
||||
return a !== null && b !== null;
|
||||
}
|
88
src/pages/api/auth/oauth/discord.ts
Normal file
88
src/pages/api/auth/oauth/discord.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
import prisma from 'lib/prisma';
|
||||
import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
|
||||
import { createToken, getBase64URLFromURL, notNull } from 'lib/util';
|
||||
import Logger from 'lib/logger';
|
||||
import config from 'lib/config';
|
||||
import { discord_auth } from 'lib/oauth';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
if (!config.features.oauth_registration) return res.forbid('oauth registration disabled');
|
||||
|
||||
if (!notNull(config.oauth.discord_client_id, config.oauth.discord_client_secret)) {
|
||||
Logger.get('oauth').error('Discord OAuth is not configured');
|
||||
return res.bad('Discord OAuth is not configured');
|
||||
}
|
||||
|
||||
const { code } = req.query as { code: string };
|
||||
if (!code) return res.bad('no code');
|
||||
|
||||
const resp = await fetch('https://discord.com/api/oauth2/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
client_id: config.oauth.discord_client_id,
|
||||
client_secret: config.oauth.discord_client_secret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: `${config.core.https ? 'https' : 'http'}://${req.headers.host}/api/auth/oauth/discord`,
|
||||
scope: 'identify',
|
||||
}),
|
||||
});
|
||||
if (!resp.ok) return res.error('invalid request');
|
||||
const json = await resp.json();
|
||||
|
||||
if (!json.access_token) return res.error('no access_token in response');
|
||||
|
||||
const userJson = await discord_auth.oauth_user(json.access_token);
|
||||
if (!userJson) return res.error('invalid user request');
|
||||
|
||||
const avatar = userJson.avatar ? `https://cdn.discordapp.com/avatars/${userJson.id}/${userJson.avatar}.png` : `https://cdn.discordapp.com/embed/avatars/${userJson.discriminator % 5}.png`;
|
||||
const avatarBase64 = await getBase64URLFromURL(avatar);
|
||||
|
||||
const existing = await prisma.user.findFirst({
|
||||
where: {
|
||||
username: userJson.username,
|
||||
},
|
||||
});
|
||||
|
||||
if (existing && existing.oauth && existing.oauthProvider === 'discord') {
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: existing.id,
|
||||
},
|
||||
data: {
|
||||
oauthAccessToken: json.access_token,
|
||||
},
|
||||
});
|
||||
|
||||
req.cleanCookie('user');
|
||||
res.setCookie('user', existing.id, { sameSite: true, expires: new Date(Date.now() + (6.048e+8 * 2)), path: '/' });
|
||||
Logger.get('user').info(`User ${existing.username} (${existing.id}) logged in via oauth(discord)`);
|
||||
|
||||
return res.redirect('/dashboard');
|
||||
} else if (existing) {
|
||||
return res.forbid('username is already taken');
|
||||
}
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: userJson.username,
|
||||
token: createToken(),
|
||||
oauth: true,
|
||||
oauthProvider: 'discord',
|
||||
oauthAccessToken: json.access_token,
|
||||
avatar: avatarBase64,
|
||||
},
|
||||
});
|
||||
Logger.get('user').info(`Created user ${user.username} via oauth(discord)`);
|
||||
|
||||
req.cleanCookie('user');
|
||||
res.setCookie('user', user.id, { sameSite: true, expires: new Date(Date.now() + (6.048e+8 * 2)), path: '/' });
|
||||
Logger.get('user').info(`User ${user.username} (${user.id}) logged in via oauth(discord)`);
|
||||
|
||||
return res.redirect('/dashboard');
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
88
src/pages/api/auth/oauth/github.ts
Normal file
88
src/pages/api/auth/oauth/github.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
import prisma from 'lib/prisma';
|
||||
import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
|
||||
import { createToken, getBase64URLFromURL, notNull } from 'lib/util';
|
||||
import Logger from 'lib/logger';
|
||||
import config from 'lib/config';
|
||||
import { github_auth } from 'lib/oauth';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
if (!config.features.oauth_registration) return res.forbid('oauth registration disabled');
|
||||
|
||||
if (!notNull(config.oauth.github_client_id, config.oauth.github_client_secret)) {
|
||||
Logger.get('oauth').error('GitHub OAuth is not configured');
|
||||
return res.bad('GitHub OAuth is not configured');
|
||||
}
|
||||
|
||||
const { code } = req.query as { code: string };
|
||||
|
||||
if (!code) return res.bad('no code');
|
||||
|
||||
const resp = await fetch('https://github.com/login/oauth/access_token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
client_id: config.oauth.github_client_id,
|
||||
client_secret: config.oauth.github_client_secret,
|
||||
code,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!resp.ok) return res.error('invalid request');
|
||||
|
||||
const json = await resp.json();
|
||||
|
||||
if (!json.access_token) return res.error('no access_token in response');
|
||||
|
||||
const userJson = await github_auth.oauth_user(json.access_token);
|
||||
if (!userJson) return res.error('invalid user request');
|
||||
|
||||
const avatarBase64 = await getBase64URLFromURL(userJson.avatar_url);
|
||||
|
||||
const existing = await prisma.user.findFirst({
|
||||
where: {
|
||||
username: userJson.login,
|
||||
},
|
||||
});
|
||||
|
||||
if (existing && existing.oauth && existing.oauthProvider === 'github') {
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: existing.id,
|
||||
},
|
||||
data: {
|
||||
oauthAccessToken: json.access_token,
|
||||
},
|
||||
});
|
||||
|
||||
req.cleanCookie('user');
|
||||
res.setCookie('user', existing.id, { sameSite: true, expires: new Date(Date.now() + (6.048e+8 * 2)), path: '/' });
|
||||
Logger.get('user').info(`User ${existing.username} (${existing.id}) logged in via oauth(github)`);
|
||||
|
||||
return res.redirect('/dashboard');
|
||||
} else if (existing) {
|
||||
return res.forbid('username is already taken');
|
||||
}
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: userJson.login,
|
||||
token: createToken(),
|
||||
oauth: true,
|
||||
oauthProvider: 'github',
|
||||
oauthAccessToken: json.access_token,
|
||||
avatar: avatarBase64,
|
||||
},
|
||||
});
|
||||
Logger.get('user').info(`Created user ${user.username} via oauth(github)`);
|
||||
|
||||
req.cleanCookie('user');
|
||||
res.setCookie('user', user.id, { sameSite: true, expires: new Date(Date.now() + (6.048e+8 * 2)), path: '/' });
|
||||
Logger.get('user').info(`User ${user.username} (${user.id}) logged in via oauth(github)`);
|
||||
|
||||
return res.redirect('/dashboard');
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
|
@ -2,11 +2,38 @@ import prisma from 'lib/prisma';
|
|||
import { hashPassword } from 'lib/util';
|
||||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import Logger from 'lib/logger';
|
||||
import config from 'lib/config';
|
||||
import { discord_auth, github_auth } from 'lib/oauth';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
if (user.oauth) {
|
||||
// this will probably change before the stable release
|
||||
if (user.oauthProvider === 'github') {
|
||||
const resp = await github_auth.oauth_user(user.oauthAccessToken);
|
||||
if (!resp) {
|
||||
req.cleanCookie('user');
|
||||
Logger.get('user').info(`User ${user.username} (${user.id}) logged out (oauth token expired)`);
|
||||
|
||||
return res.json({ error: 'oauth token expired', redirect_uri: github_auth.oauth_url(config.oauth.github_client_id) });
|
||||
}
|
||||
} else if (user.oauthProvider === 'discord') {
|
||||
const resp = await fetch('https://discord.com/api/users/@me', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${user.oauthAccessToken}`,
|
||||
},
|
||||
});
|
||||
if (!resp.ok) {
|
||||
req.cleanCookie('user');
|
||||
Logger.get('user').info(`User ${user.username} (${user.id}) logged out (oauth token expired)`);
|
||||
|
||||
return res.json({ error: 'oauth token expired', redirect_uri: discord_auth.oauth_url(config.oauth.discord_client_id, `${config.core.https ? 'https' : 'http'}://${req.headers.host}`) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method === 'PATCH') {
|
||||
if (req.body.password) {
|
||||
const hashed = await hashPassword(req.body.password);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { Button, Center, TextInput, Title, PasswordInput } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import Link from 'next/link';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
export { getServerSideProps } from 'middleware/getServerSideProps';
|
||||
|
||||
export default function Login() {
|
||||
export default function Login({ oauth_registration }) {
|
||||
const router = useRouter();
|
||||
|
||||
const form = useForm({
|
||||
|
@ -54,10 +56,13 @@ export default function Login() {
|
|||
|
||||
<Button size='lg' type='submit' fullWidth mt={12}>Login</Button>
|
||||
</form>
|
||||
{oauth_registration && (
|
||||
<Link href='/auth/register' passHref>
|
||||
<Button size='lg' fullWidth mt={12} component='a'>Register</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Login.title = 'Zipline - Login';
|
||||
}
|
49
src/pages/auth/register.tsx
Normal file
49
src/pages/auth/register.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { Button, Center, TextInput, Title, PasswordInput } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import Link from 'next/link';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import GitHubIcon from 'components/icons/GitHubIcon';
|
||||
import DiscordIcon from 'components/icons/DiscordIcon';
|
||||
export { getServerSideProps } from 'middleware/getServerSideProps';
|
||||
|
||||
export default function Login({ oauth_registration, oauth_providers: unparsed }) {
|
||||
const oauth_providers = JSON.parse(unparsed);
|
||||
|
||||
const icons = {
|
||||
GitHub: GitHubIcon,
|
||||
Discord: DiscordIcon,
|
||||
};
|
||||
|
||||
for (const provider of oauth_providers) {
|
||||
provider.Icon = icons[provider.name];
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
if (!oauth_registration) {
|
||||
router.push('/auth/login');
|
||||
return null;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const a = await fetch('/api/user');
|
||||
if (a.ok) await router.push('/dashboard');
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center sx={{ height: '100vh' }}>
|
||||
<div>
|
||||
{oauth_providers.map(({ url, name, Icon }, i) => (
|
||||
<Link key={i} href={url} passHref>
|
||||
<Button size='lg' fullWidth mt={12} leftIcon={<Icon />} component='a'>Sign in with {name}</Button>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue