initial commit: fork
This commit is contained in:
28
packages/auth/env.mjs
Normal file
28
packages/auth/env.mjs
Normal file
@@ -0,0 +1,28 @@
|
||||
import { createEnv } from '@t3-oss/env-nextjs';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
DISCORD_CLIENT_ID: z.string().min(1),
|
||||
DISCORD_CLIENT_SECRET: z.string().min(1),
|
||||
NEXTAUTH_SECRET:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? z.string().min(1)
|
||||
: z.string().min(1).optional(),
|
||||
NEXTAUTH_URL: z.preprocess(
|
||||
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
|
||||
// Since NextAuth.js automatically uses the VERCEL_URL if present.
|
||||
str => process.env.VERCEL_URL ?? str,
|
||||
// VERCEL_URL doesn't include `https` so it cant be validated as a URL
|
||||
process.env.VERCEL ? z.string() : z.string().url()
|
||||
)
|
||||
},
|
||||
client: {},
|
||||
runtimeEnv: {
|
||||
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
||||
DISCORD_CLIENT_ID: process.env.DISCORD_CLIENT_ID,
|
||||
DISCORD_CLIENT_SECRET: process.env.DISCORD_CLIENT_SECRET
|
||||
},
|
||||
skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION
|
||||
});
|
||||
134
packages/auth/index.ts
Normal file
134
packages/auth/index.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
// @ts-nocheck
|
||||
import Discord, { type DiscordProfile } from '@auth/core/providers/discord';
|
||||
import type { DefaultSession as DefaultSessionType } from '@auth/core/types';
|
||||
import { PrismaAdapter } from '@auth/prisma-adapter';
|
||||
import { prisma } from '@master-bot/db';
|
||||
import NextAuth from 'next-auth';
|
||||
|
||||
import { env } from './env.mjs';
|
||||
|
||||
export type { Session } from 'next-auth';
|
||||
|
||||
// Update this whenever adding new providers so that the client can
|
||||
export const providers = ['discord'] as const;
|
||||
export type OAuthProviders = (typeof providers)[number];
|
||||
|
||||
declare module 'next-auth' {
|
||||
interface Session {
|
||||
user: {
|
||||
id: string;
|
||||
discordId: string;
|
||||
} & DefaultSessionType['user'];
|
||||
}
|
||||
}
|
||||
|
||||
const scope = ['identify', 'guilds', 'email'].join(' ');
|
||||
|
||||
export const {
|
||||
handlers: { GET, POST },
|
||||
auth,
|
||||
CSRF_experimental
|
||||
} = NextAuth({
|
||||
adapter: {
|
||||
...PrismaAdapter(prisma),
|
||||
createUser: async data => {
|
||||
return await prisma.user.upsert({
|
||||
where: { discordId: data.discordId },
|
||||
update: data,
|
||||
create: data
|
||||
});
|
||||
}
|
||||
},
|
||||
providers: [
|
||||
Discord({
|
||||
clientId: env.DISCORD_CLIENT_ID,
|
||||
clientSecret: env.DISCORD_CLIENT_SECRET,
|
||||
authorization: {
|
||||
params: {
|
||||
scope
|
||||
}
|
||||
},
|
||||
profile(profile: DiscordProfile) {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.username,
|
||||
email: profile.email,
|
||||
image: profile.avatar,
|
||||
discordId: profile.id
|
||||
};
|
||||
}
|
||||
})
|
||||
],
|
||||
callbacks: {
|
||||
session: async ({ session, user }) => {
|
||||
const account = await prisma.account.findUnique({
|
||||
where: {
|
||||
userId: user.id
|
||||
}
|
||||
});
|
||||
|
||||
if (account?.expires_at * 1000 < Date.now()) {
|
||||
// refresh token
|
||||
try {
|
||||
const response = await fetch(
|
||||
'https://discord.com/api/v10/oauth2/token',
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
client_id: env.DISCORD_CLIENT_ID,
|
||||
client_secret: env.DISCORD_CLIENT_SECRET,
|
||||
refresh_token: account.refresh_token
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to refresh token');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
await prisma.account.update({
|
||||
where: {
|
||||
userId: user.id
|
||||
},
|
||||
data: {
|
||||
access_token: data.access_token,
|
||||
refresh_token: data.refresh_token,
|
||||
expires_at: data.expires_in
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...session,
|
||||
user: {
|
||||
...session.user,
|
||||
id: user.id,
|
||||
discordId: user.discordId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// @TODO - if you wanna have auth on the edge
|
||||
// jwt: ({ token, profile }) => {
|
||||
// if (profile?.id) {
|
||||
// token.id = profile.id;
|
||||
// token.image = profile.picture;
|
||||
// }
|
||||
// return token;
|
||||
// },
|
||||
|
||||
// @TODO
|
||||
// authorized({ request, auth }) {
|
||||
// return !!auth?.user
|
||||
// }
|
||||
}
|
||||
});
|
||||
35
packages/auth/package.json
Normal file
35
packages/auth/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@master-bot/auth",
|
||||
"version": "0.1.0",
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.10.0",
|
||||
"@auth/prisma-adapter": "^1.0.1",
|
||||
"@master-bot/db": "^0.1.0",
|
||||
"@t3-oss/env-nextjs": "^0.6.0",
|
||||
"next": "^13.4.12",
|
||||
"next-auth": "^0.0.0-manual.b53ca00b",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@master-bot/eslint-config": "^0.2.0",
|
||||
"eslint": "^8.46.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@master-bot/eslint-config/base"
|
||||
]
|
||||
}
|
||||
}
|
||||
4
packages/auth/tsconfig.json
Normal file
4
packages/auth/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src", "*.ts", "env.mjs"]
|
||||
}
|
||||
Reference in New Issue
Block a user