feat(server): port resolvers to node server (#2026)

Co-authored-by: Himself65 <himself65@outlook.com>
This commit is contained in:
liuyi
2023-04-28 07:02:05 +08:00
committed by GitHub
parent 3df3498523
commit b4bb57b2a5
26 changed files with 807 additions and 191 deletions
+82
View File
@@ -0,0 +1,82 @@
import {
CanActivate,
createParamDecorator,
ExecutionContext,
Injectable,
UseGuards,
} from '@nestjs/common';
import { PrismaService } from '../../prisma';
import { getRequestResponseFromContext } from '../../utils/nestjs';
import { AuthService } from './service';
export function getUserFromContext(context: ExecutionContext) {
const req = getRequestResponseFromContext(context).req;
return req.user;
}
/**
* Used to fetch current user from the request context.
*
* > The user may be undefined if authorization token is not provided.
*
* @example
*
* ```typescript
* // Graphql Query
* \@Query(() => UserType)
* user(@CurrentUser() user?: User) {
* return user;
* }
* ```
*
* ```typescript
* // HTTP Controller
* \@Get('/user)
* user(@CurrentUser() user?: User) {
* return user;
* }
* ```
*/
export const CurrentUser = createParamDecorator(
(_: unknown, context: ExecutionContext) => {
return getUserFromContext(context);
}
);
@Injectable()
class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private prisma: PrismaService) {}
async canActivate(context: ExecutionContext) {
const { req } = getRequestResponseFromContext(context);
const token = req.headers.authorization;
if (!token) {
return false;
}
const claims = this.auth.verify(token);
req.user = await this.prisma.user.findUnique({ where: { id: claims.id } });
return !!req.user;
}
}
/**
* This guard is used to protect routes/queries/mutations that require a user to be logged in.
*
* The `@CurrentUser()` parameter decorator used in a `Auth` guarded queries would always give us the user because the `Auth` guard will
* fast throw if user is not logged in.
*
* @example
*
* ```typescript
* \@Auth()
* \@Query(() => UserType)
* user(@CurrentUser() user: User) {
* return user;
* }
* ```
*/
export const Auth = () => {
return UseGuards(AuthGuard);
};
+12
View File
@@ -0,0 +1,12 @@
import { Global, Module } from '@nestjs/common';
import { AuthResolver } from './resolver';
import { AuthService } from './service';
@Global()
@Module({
providers: [AuthService, AuthResolver],
exports: [AuthService],
})
export class AuthModule {}
export * from './guard';
+54
View File
@@ -0,0 +1,54 @@
import { ForbiddenException } from '@nestjs/common';
import {
Args,
Context,
Field,
Mutation,
ObjectType,
Parent,
ResolveField,
Resolver,
} from '@nestjs/graphql';
import { Request } from 'express';
import { UserType } from '../users/resolver';
import { CurrentUser } from './guard';
import { AuthService } from './service';
@ObjectType()
export class TokenType {
@Field()
token!: string;
@Field()
refresh!: string;
}
@Resolver(() => UserType)
export class AuthResolver {
constructor(private auth: AuthService) {}
@ResolveField(() => TokenType)
token(@CurrentUser() currentUser: UserType, @Parent() user: UserType) {
if (user !== currentUser) {
throw new ForbiddenException();
}
return {
token: this.auth.sign(user),
// TODO: impl
refresh: '',
};
}
@Mutation(() => UserType)
async signIn(
@Context() ctx: { req: Request },
@Args('email') email: string,
@Args('password') password: string
) {
const user = await this.auth.signIn(email, password);
ctx.req.user = user;
return user;
}
}
+45
View File
@@ -0,0 +1,45 @@
import {
BadRequestException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { User } from '@prisma/client';
import jwt from 'jsonwebtoken';
import { Config } from '../../config';
import { PrismaService } from '../../prisma';
type UserClaim = Pick<User, 'id' | 'name' | 'email'>;
@Injectable()
export class AuthService {
constructor(private config: Config, private prisma: PrismaService) {}
sign(user: UserClaim) {
return jwt.sign(user, this.config.secret);
}
verify(token: string) {
try {
const claims = jwt.verify(token, this.config.secret) as UserClaim;
return claims;
} catch (e) {
throw new UnauthorizedException('Invalid token');
}
}
async signIn(email: string, password: string) {
const user = await this.prisma.user.findFirst({
where: {
email,
password,
},
});
if (!user) {
throw new BadRequestException('Invalid email or password');
}
return user;
}
}