feat: update server login feature (#3004)

Co-authored-by: LongYinan <lynweklm@gmail.com>
This commit is contained in:
Alex Yang
2023-07-05 11:13:20 +08:00
committed by GitHub
parent 9cd1f013f8
commit 3084c427f1
7 changed files with 127 additions and 305 deletions

View File

@@ -1,2 +1,2 @@
SECRET_KEY="secret"
DATABASE_URL="postgresql://affine@localhost:5432/affine"
NEXTAUTH_URL="http://localhost:8080"

View File

@@ -27,6 +27,7 @@
"@node-rs/crc32": "^1.7.0",
"@node-rs/jsonwebtoken": "^0.2.0",
"@prisma/client": "^4.16.2",
"cookie-parser": "^1.4.6",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"graphql": "^16.7.1",
@@ -34,6 +35,7 @@
"graphql-upload": "^16.0.2",
"lodash-es": "^4.17.21",
"next-auth": "^4.22.1",
"nodemailer": "^6.9.3",
"parse-duration": "^1.1.0",
"prisma": "^4.16.2",
"reflect-metadata": "^0.1.13",
@@ -43,9 +45,11 @@
"@affine/storage": "workspace:*",
"@napi-rs/image": "^1.6.1",
"@nestjs/testing": "^10.0.4",
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.17",
"@types/lodash-es": "^4.17.7",
"@types/node": "^18.16.19",
"@types/nodemailer": "^6.4.8",
"@types/supertest": "^2.0.12",
"c8": "^8.0.0",
"nodemon": "^2.0.22",

View File

@@ -233,5 +233,11 @@ export interface AFFiNEConfig {
}
>
>;
email: {
server: string;
port: number;
sender: string;
password: string;
};
};
}

View File

@@ -65,6 +65,10 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
OAUTH_GOOGLE_CLIENT_SECRET: 'auth.oauthProviders.google.clientSecret',
OAUTH_GITHUB_CLIENT_ID: 'auth.oauthProviders.github.clientId',
OAUTH_GITHUB_CLIENT_SECRET: 'auth.oauthProviders.github.clientSecret',
OAUTH_EMAIL_SENDER: 'auth.email.sender',
OAUTH_EMAIL_SERVER: 'auth.email.server',
OAUTH_EMAIL_PORT: 'auth.email.port',
OAUTH_EMAIL_PASSWORD: 'auth.email.password',
} satisfies AFFiNEConfig['ENV_MAP'],
env: process.env.NODE_ENV ?? 'development',
get prod() {
@@ -102,7 +106,6 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
},
introspection: true,
playground: true,
debug: true,
},
auth: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -114,8 +117,16 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
publicKey: jwtKeyPair.publicKey,
enableSignup: true,
enableOauth: false,
nextAuthSecret: '',
get nextAuthSecret() {
return this.privateKey;
},
oauthProviders: {},
email: {
server: 'smtp.gmail.com',
port: 465,
sender: '',
password: '',
},
},
objectStorage: {
r2: {
@@ -129,7 +140,7 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
path: join(homedir(), '.affine-storage'),
},
},
} as const;
} satisfies AFFiNEConfig;
applyEnvToConfig(defaultConfig);

View File

@@ -1,6 +1,7 @@
/// <reference types="./global.d.ts" />
import { NestFactory } from '@nestjs/core';
import type { NestExpressApplication } from '@nestjs/platform-express';
import cookieParser from 'cookie-parser';
import { static as staticMiddleware } from 'express';
// @ts-expect-error graphql-upload is not typed
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
@@ -27,6 +28,8 @@ app.use(
})
);
app.use(cookieParser());
const config = app.get(Config);
const host = config.host ?? 'localhost';

View File

@@ -2,11 +2,10 @@ import { randomUUID } from 'node:crypto';
import { PrismaAdapter } from '@auth/prisma-adapter';
import {
All,
BadRequestException,
Controller,
Get,
Next,
Post,
Query,
Req,
Res,
@@ -15,6 +14,7 @@ import { Algorithm, sign, verify as jwtVerify } from '@node-rs/jsonwebtoken';
import type { NextFunction, Request, Response } from 'express';
import type { AuthAction, AuthOptions } from 'next-auth';
import { AuthHandler } from 'next-auth/core';
import Email from 'next-auth/providers/email';
import Github from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
@@ -29,15 +29,40 @@ export class NextAuthController {
private readonly nextAuthOptions: AuthOptions;
constructor(readonly config: Config, readonly prisma: PrismaService) {
const prismaAdapter = PrismaAdapter(prisma);
// createUser exists in the adapter
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const createUser = prismaAdapter.createUser!.bind(prismaAdapter);
prismaAdapter.createUser = async data => {
if (data.email && !data.name) {
data.name = data.email.split('@')[0];
}
return createUser(data);
};
this.nextAuthOptions = {
providers: [],
providers: [
// @ts-expect-error esm interop issue
Email.default({
server: {
host: config.auth.email.server,
port: config.auth.email.port,
auth: {
user: config.auth.email.sender,
pass: config.auth.email.password,
},
},
from: `AFFiNE <no-reply@toeverything.info>`,
}),
],
// @ts-expect-error Third part library type mismatch
adapter: PrismaAdapter(prisma),
adapter: prismaAdapter,
debug: !config.prod,
};
if (config.auth.oauthProviders.github) {
this.nextAuthOptions.providers.push(
Github({
// @ts-expect-error esm interop issue
Github.default({
clientId: config.auth.oauthProviders.github.clientId,
clientSecret: config.auth.oauthProviders.github.clientSecret,
})
@@ -46,12 +71,14 @@ export class NextAuthController {
if (config.auth.oauthProviders.google) {
this.nextAuthOptions.providers.push(
Google({
// @ts-expect-error esm interop issue
Google.default({
clientId: config.auth.oauthProviders.google.clientId,
clientSecret: config.auth.oauthProviders.google.clientSecret,
})
);
}
this.nextAuthOptions.jwt = {
encode: async ({ token, maxAge }) => {
if (!token?.email) {
@@ -108,8 +135,7 @@ export class NextAuthController {
this.nextAuthOptions.secret ??= config.auth.nextAuthSecret;
}
@Get()
@Post()
@All('*')
async auth(
@Req() req: Request,
@Res() res: Response,