mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
fix: allow login with credentials on production (#4288)
This commit is contained in:
@@ -77,6 +77,10 @@ class AuthGuard implements CanActivate {
|
||||
if (isPublic) {
|
||||
return true;
|
||||
} else if (!token) {
|
||||
if (!req.cookies) {
|
||||
return isPublicable;
|
||||
}
|
||||
|
||||
const session = await AuthHandler({
|
||||
req: {
|
||||
cookies: req.cookies,
|
||||
|
||||
@@ -85,7 +85,7 @@ export const NextAuthOptionsProvider: FactoryProvider<NextAuthOptions> = {
|
||||
adapter: prismaAdapter,
|
||||
debug: !config.node.prod,
|
||||
session: {
|
||||
strategy: config.node.prod ? 'database' : 'jwt',
|
||||
strategy: 'database',
|
||||
},
|
||||
logger: {
|
||||
debug(code, metadata) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { hash, verify } from '@node-rs/argon2';
|
||||
import type { User } from '@prisma/client';
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
import { pick } from 'lodash-es';
|
||||
import type { AuthAction, NextAuthOptions } from 'next-auth';
|
||||
import type { AuthAction, CookieOption, NextAuthOptions } from 'next-auth';
|
||||
import { AuthHandler } from 'next-auth/core';
|
||||
|
||||
import { Config } from '../../config';
|
||||
@@ -29,6 +29,8 @@ import { AuthService } from './service';
|
||||
|
||||
const BASE_URL = '/api/auth/';
|
||||
|
||||
const DEFAULT_SESSION_EXPIRE_DATE = 2592000 * 1000; // 30 days
|
||||
|
||||
@Controller(BASE_URL)
|
||||
export class NextAuthController {
|
||||
private readonly callbackSession;
|
||||
@@ -69,7 +71,11 @@ export class NextAuthController {
|
||||
.slice(BASE_URL.length) // make relative to baseUrl
|
||||
.replace(/\?.*/, '') // remove query part, use only path part
|
||||
.split('/') as [AuthAction, string]; // as array of strings;
|
||||
if (providerId === 'credentials') {
|
||||
|
||||
const credentialsSignIn =
|
||||
req.method === 'POST' && providerId === 'credentials';
|
||||
let userId: string | undefined;
|
||||
if (credentialsSignIn) {
|
||||
const { email } = req.body;
|
||||
if (email) {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
@@ -83,6 +89,7 @@ export class NextAuthController {
|
||||
req.body = null;
|
||||
throw new NotFoundException(`User not found`);
|
||||
} else {
|
||||
userId = user.id;
|
||||
req.body = {
|
||||
...req.body,
|
||||
name: user.name,
|
||||
@@ -140,6 +147,29 @@ export class NextAuthController {
|
||||
}
|
||||
}
|
||||
|
||||
let nextAuthTokenCookie: (CookieOption & { value: string }) | undefined;
|
||||
// next-auth credentials login only support JWT strategy
|
||||
// https://next-auth.js.org/configuration/providers/credentials
|
||||
// let's store the session token in the database
|
||||
if (
|
||||
credentialsSignIn &&
|
||||
(nextAuthTokenCookie = cookies?.find(
|
||||
({ name }) => name === 'next-auth.session-token'
|
||||
))
|
||||
) {
|
||||
const cookieExpires = new Date();
|
||||
cookieExpires.setTime(
|
||||
cookieExpires.getTime() + DEFAULT_SESSION_EXPIRE_DATE
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await this.nextAuthOptions.adapter!.createSession!({
|
||||
sessionToken: nextAuthTokenCookie.value,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
userId: userId!,
|
||||
expires: cookieExpires,
|
||||
});
|
||||
}
|
||||
|
||||
if (redirect?.endsWith('api/auth/error?error=AccessDenied')) {
|
||||
this.logger.log(`Early access redirect headers: ${req.headers}`);
|
||||
this.metrics.authFailCounter(1, {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
import ava, { TestFn } from 'ava';
|
||||
// @ts-expect-error graphql-upload is not typed
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
|
||||
@@ -21,28 +21,26 @@ import {
|
||||
signUp,
|
||||
} from './utils';
|
||||
|
||||
let app: INestApplication;
|
||||
const test = ava as TestFn<{
|
||||
app: INestApplication;
|
||||
client: PrismaClient;
|
||||
auth: AuthService;
|
||||
mail: MailService;
|
||||
}>;
|
||||
|
||||
const client = new PrismaClient();
|
||||
|
||||
let auth: AuthService;
|
||||
let mail: MailService;
|
||||
|
||||
// cleanup database before each test
|
||||
test.beforeEach(async () => {
|
||||
test.beforeEach(async t => {
|
||||
const client = new PrismaClient();
|
||||
t.context.client = client;
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
const app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
@@ -51,15 +49,19 @@ test.beforeEach(async () => {
|
||||
);
|
||||
await app.init();
|
||||
|
||||
auth = module.get(AuthService);
|
||||
mail = module.get(MailService);
|
||||
const auth = module.get(AuthService);
|
||||
const mail = module.get(MailService);
|
||||
t.context.app = app;
|
||||
t.context.auth = auth;
|
||||
t.context.mail = mail;
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await app.close();
|
||||
test.afterEach(async t => {
|
||||
await t.context.app.close();
|
||||
});
|
||||
|
||||
test('should invite a user', async t => {
|
||||
const { app } = t.context;
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
@@ -76,6 +78,7 @@ test('should invite a user', async t => {
|
||||
});
|
||||
|
||||
test('should accept an invite', async t => {
|
||||
const { app } = t.context;
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
@@ -94,6 +97,7 @@ test('should accept an invite', async t => {
|
||||
});
|
||||
|
||||
test('should leave a workspace', async t => {
|
||||
const { app } = t.context;
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
@@ -108,6 +112,7 @@ test('should leave a workspace', async t => {
|
||||
});
|
||||
|
||||
test('should revoke a user', async t => {
|
||||
const { app } = t.context;
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
@@ -122,6 +127,7 @@ test('should revoke a user', async t => {
|
||||
});
|
||||
|
||||
test('should create user if not exist', async t => {
|
||||
const { app, auth } = t.context;
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
@@ -134,6 +140,7 @@ test('should create user if not exist', async t => {
|
||||
});
|
||||
|
||||
test('should invite a user by link', async t => {
|
||||
const { app } = t.context;
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
@@ -167,6 +174,7 @@ test('should invite a user by link', async t => {
|
||||
});
|
||||
|
||||
test('should send email', async t => {
|
||||
const { mail, app } = t.context;
|
||||
if (mail.hasConfigured()) {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'test', 'production@toeverything.info', '1');
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
import ava, { TestFn } from 'ava';
|
||||
// @ts-expect-error graphql-upload is not typed
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
import request from 'supertest';
|
||||
@@ -20,25 +20,23 @@ import {
|
||||
updateWorkspace,
|
||||
} from './utils';
|
||||
|
||||
let app: INestApplication;
|
||||
const test = ava as TestFn<{
|
||||
app: INestApplication;
|
||||
client: PrismaClient;
|
||||
}>;
|
||||
|
||||
const client = new PrismaClient();
|
||||
|
||||
// cleanup database before each test
|
||||
test.beforeEach(async () => {
|
||||
test.beforeEach(async t => {
|
||||
const client = new PrismaClient();
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
const app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
@@ -46,20 +44,23 @@ test.beforeEach(async () => {
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
t.context.client = client;
|
||||
t.context.app = app;
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await app.close();
|
||||
test.afterEach(async t => {
|
||||
await t.context.app.close();
|
||||
});
|
||||
|
||||
test('should register a user', async t => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '123456');
|
||||
const user = await signUp(t.context.app, 'u1', 'u1@affine.pro', '123456');
|
||||
t.is(typeof user.id, 'string', 'user.id is not a string');
|
||||
t.is(user.name, 'u1', 'user.name is not valid');
|
||||
t.is(user.email, 'u1@affine.pro', 'user.email is not valid');
|
||||
});
|
||||
|
||||
test.skip('should be throttled at call signUp', async t => {
|
||||
const { app } = t.context;
|
||||
let token = '';
|
||||
for (let i = 0; i < 10; i++) {
|
||||
token = (await signUp(app, `u${i}`, `u${i}@affine.pro`, `${i}`)).token
|
||||
@@ -72,6 +73,7 @@ test.skip('should be throttled at call signUp', async t => {
|
||||
});
|
||||
|
||||
test('should create a workspace', async t => {
|
||||
const { app } = t.context;
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
@@ -79,6 +81,7 @@ test('should create a workspace', async t => {
|
||||
});
|
||||
|
||||
test('should can publish workspace', async t => {
|
||||
const { app } = t.context;
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
|
||||
@@ -100,6 +103,7 @@ test('should can publish workspace', async t => {
|
||||
});
|
||||
|
||||
test('should can read published workspace', async t => {
|
||||
const { app } = t.context;
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
|
||||
@@ -113,6 +117,7 @@ test('should can read published workspace', async t => {
|
||||
});
|
||||
|
||||
test('should share a page', async t => {
|
||||
const { app } = t.context;
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
@@ -162,6 +167,7 @@ test('should share a page', async t => {
|
||||
});
|
||||
|
||||
test('should can get workspace doc', async t => {
|
||||
const { app } = t.context;
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '2');
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
@@ -207,6 +213,7 @@ test('should can get workspace doc', async t => {
|
||||
});
|
||||
|
||||
test('should be able to get public workspace doc', async t => {
|
||||
const { app } = t.context;
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
|
||||
Reference in New Issue
Block a user