feat(server): auth server (#2773)

This commit is contained in:
LongYinan
2023-06-21 14:08:32 +08:00
committed by GitHub
parent 2698e7fd0d
commit 9b3fa43b81
36 changed files with 4089 additions and 2013 deletions

View File

@@ -1,10 +1,14 @@
import { equal, ok } from 'node:assert';
import { afterEach, beforeEach, describe, test } from 'node:test';
import { Transformer } from '@napi-rs/image';
import type { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { hash } from '@node-rs/bcrypt';
import { hash } from '@node-rs/argon2';
import { PrismaClient } from '@prisma/client';
import { Express } from 'express';
// @ts-expect-error graphql-upload is not typed
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
import request from 'supertest';
import { AppModule } from '../app';
@@ -24,7 +28,6 @@ describe('AppModule', () => {
await client.user.deleteMany({});
await client.user.create({
data: {
id: '1',
name: 'Alex Yang',
email: 'alex.yang@example.org',
password: await hash('123456'),
@@ -36,7 +39,16 @@ describe('AppModule', () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
app = module.createNestApplication({
cors: true,
bodyParser: true,
});
app.use(
graphqlUploadExpress({
maxFileSize: 10 * 1024 * 1024,
maxFiles: 5,
})
);
await app.init();
});
@@ -57,32 +69,11 @@ describe('AppModule', () => {
})
.expect(400);
let token;
await request(app.getHttpServer())
.post(gql)
.send({
query: `
mutation {
signIn(email: "alex.yang@example.org", password: "123456") {
token {
token
}
}
}
`,
})
.expect(200)
.expect(res => {
ok(
typeof res.body.data.signIn.token.token === 'string',
'res.body.data.signIn.token.token is not a string'
);
token = res.body.data.signIn.token.token;
});
const { token } = await createToken(app);
await request(app.getHttpServer())
.post(gql)
.set({ Authorization: token })
.auth(token, { type: 'bearer' })
.send({
query: `
mutation {
@@ -116,8 +107,10 @@ describe('AppModule', () => {
});
test('should find default user', async () => {
const { token } = await createToken(app);
await request(app.getHttpServer())
.post(gql)
.auth(token, { type: 'bearer' })
.send({
query: `
query {
@@ -133,4 +126,72 @@ describe('AppModule', () => {
equal(res.body.data.user.email, 'alex.yang@example.org');
});
});
test('should be able to upload avatar', async () => {
const { token, id } = await createToken(app);
const png = await Transformer.fromRgbaPixels(
Buffer.alloc(400 * 400 * 4).fill(255),
400,
400
).png();
await request(app.getHttpServer())
.post(gql)
.auth(token, { type: 'bearer' })
.field(
'operations',
JSON.stringify({
name: 'uploadAvatar',
query: `mutation uploadAvatar($id: String!, $avatar: Upload!) {
uploadAvatar(id: $id, avatar: $avatar) {
id
name
avatarUrl
email
}
}
`,
variables: { id, avatar: null },
})
)
.field('map', JSON.stringify({ '0': ['variables.avatar'] }))
.attach('0', png, 'avatar.png')
.expect(200)
.expect(res => {
equal(res.body.data.uploadAvatar.id, id);
});
});
});
async function createToken(app: INestApplication<Express>): Promise<{
id: string;
token: string;
}> {
let token;
let id;
await request(app.getHttpServer())
.post(gql)
.send({
query: `
mutation {
signIn(email: "alex.yang@example.org", password: "123456") {
id
token {
token
}
}
}
`,
})
.expect(200)
.expect(res => {
id = res.body.data.signIn.id;
ok(
typeof res.body.data.signIn.token.token === 'string',
'res.body.data.signIn.token.token is not a string'
);
token = res.body.data.signIn.token.token;
});
return { token: token!, id: id! };
}

View File

@@ -1,7 +1,6 @@
import { ok, throws } from 'node:assert';
import { ok } from 'node:assert';
import { beforeEach, test } from 'node:test';
import { UnauthorizedException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
@@ -28,8 +27,9 @@ beforeEach(async () => {
imports: [
ConfigModule.forRoot({
auth: {
accessTokenExpiresIn: '1s',
refreshTokenExpiresIn: '3s',
accessTokenExpiresIn: 1,
refreshTokenExpiresIn: 1,
leeway: 1,
},
}),
PrismaModule,
@@ -40,12 +40,6 @@ beforeEach(async () => {
auth = module.get(AuthService);
});
async function sleep(ms: number) {
return new Promise<void>(resolve => {
setTimeout(resolve, ms);
});
}
test('should be able to register and signIn', async () => {
await auth.register('Alex Yang', 'alexyang@example.org', '123456');
await auth.signIn('alexyang@example.org', '123456');
@@ -58,23 +52,20 @@ test('should be able to verify', async () => {
id: '1',
name: 'Alex Yang',
email: 'alexyang@example.org',
createdAt: new Date(),
};
{
const token = auth.sign(user);
const clain = auth.verify(token);
ok(clain.id === '1');
ok(clain.name === 'Alex Yang');
ok(clain.email === 'alexyang@example.org');
await sleep(1050);
throws(() => auth.verify(token), UnauthorizedException, 'Invalid token');
const token = await auth.sign(user);
const claim = await auth.verify(token);
ok(claim.id === '1');
ok(claim.name === 'Alex Yang');
ok(claim.email === 'alexyang@example.org');
}
{
const token = auth.refresh(user);
const clain = auth.verify(token);
ok(clain.id === '1');
ok(clain.name === 'Alex Yang');
ok(clain.email === 'alexyang@example.org');
await sleep(3050);
throws(() => auth.verify(token), UnauthorizedException, 'Invalid token');
const token = await auth.refresh(user);
const claim = await auth.verify(token);
ok(claim.id === '1');
ok(claim.name === 'Alex Yang');
ok(claim.email === 'alexyang@example.org');
}
});