Skip to content

Commit

Permalink
feat(server): implement pat schema & apis (#597)
Browse files Browse the repository at this point in the history
  • Loading branch information
maslow authored Jan 1, 2023
1 parent 2fbe643 commit d4da55a
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 4 deletions.
14 changes: 13 additions & 1 deletion server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
profile UserProfile?
profile UserProfile?
personalAccessTokens PersonalAccessToken[]
}

model UserProfile {
Expand Down Expand Up @@ -189,3 +190,14 @@ model CronTrigger {
cloudFunction CloudFunction @relation(fields: [appid, target], references: [appid, name])
}

model PersonalAccessToken {
id String @id @default(auto()) @map("_id") @db.ObjectId
uid String @db.ObjectId
name String
token String @unique
expiredAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [uid], references: [id])
}
29 changes: 28 additions & 1 deletion server/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { Controller, Get, Query, Req, Res, UseGuards } from '@nestjs/common'
import {
Body,
Controller,
Get,
Post,
Query,
Req,
Res,
UseGuards,
} from '@nestjs/common'
import {
ApiBearerAuth,
ApiOperation,
Expand All @@ -11,6 +20,7 @@ import { IRequest } from '../utils/types'
import { UserDto } from '../user/dto/user.response'
import { AuthService } from './auth.service'
import { JwtAuthGuard } from './jwt.auth.guard'
import { Pat2TokenDto } from './dto/pat2token.dto'

@ApiTags('Authentication')
@Controller()
Expand Down Expand Up @@ -58,6 +68,23 @@ export class AuthController {
return ResponseUtil.ok(token)
}

/**
* Get user token by PAT
* @param pat
* @returns
*/
@ApiOperation({ summary: 'Get user token by PAT' })
@ApiResponse({ type: ResponseUtil })
@Post('pat2token')
async pat2token(@Body() dto: Pat2TokenDto) {
const token = await this.authService.pat2token(dto.pat)
if (!token) {
return ResponseUtil.error('invalid pat')
}

return ResponseUtil.ok(token)
}

/**
* Get current user profile
* @param request
Expand Down
10 changes: 9 additions & 1 deletion server/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { CasdoorService } from './casdoor.service'
import { JwtStrategy } from './jwt.strategy'
import { AuthController } from './auth.controller'
import { HttpModule } from '@nestjs/axios'
import { PatService } from 'src/user/pat.service'
import { PrismaService } from 'src/prisma.service'

@Module({
imports: [
Expand All @@ -19,7 +21,13 @@ import { HttpModule } from '@nestjs/axios'
UserModule,
HttpModule,
],
providers: [AuthService, JwtStrategy, CasdoorService],
providers: [
AuthService,
JwtStrategy,
CasdoorService,
PatService,
PrismaService,
],
exports: [AuthService],
controllers: [AuthController],
})
Expand Down
18 changes: 18 additions & 0 deletions server/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { User } from '@prisma/client'
import { UserService } from '../user/user.service'
import { ServerConfig } from '../constants'
import * as assert from 'node:assert'
import { PatService } from 'src/user/pat.service'

@Injectable()
export class AuthService {
Expand All @@ -13,6 +14,7 @@ export class AuthService {
private readonly jwtService: JwtService,
private readonly casdoorService: CasdoorService,
private readonly userService: UserService,
private readonly patService: PatService,
) {}

/**
Expand Down Expand Up @@ -70,6 +72,22 @@ export class AuthService {
}
}

/**
* Get token by PAT
* @param user
* @param token
* @returns
*/
async pat2token(token: string): Promise<string> {
const pat = await this.patService.findOne(token)
if (!pat) return null

// check pat expired
if (pat.expiredAt < new Date()) return null

return this.getAccessTokenByUser(pat.user)
}

/**
* Get access token by user
* @param user
Expand Down
13 changes: 13 additions & 0 deletions server/src/auth/dto/pat2token.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsNotEmpty, IsString } from 'class-validator'

export class Pat2TokenDto {
@ApiProperty({
description: 'PAT',
example:
'laf_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
})
@IsString()
@IsNotEmpty()
pat: string
}
24 changes: 24 additions & 0 deletions server/src/user/dto/create-pat.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger'
import {
IsNotEmpty,
IsNumber,
IsString,
Length,
Max,
Min,
} from 'class-validator'

export class CreatePATDto {
@IsString()
@IsNotEmpty()
@Length(1, 255)
@ApiProperty()
name: string

@IsNumber()
@IsNotEmpty()
@Min(60)
@Max(3600 * 24 * 365)
@ApiProperty({ minimum: 60 })
expiresIn: number
}
84 changes: 84 additions & 0 deletions server/src/user/pat.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
Body,
Controller,
Delete,
Get,
Logger,
Param,
Post,
Req,
UseGuards,
} from '@nestjs/common'
import {
ApiBearerAuth,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger'
import { JwtAuthGuard } from 'src/auth/jwt.auth.guard'
import { ResponseUtil } from 'src/utils/response'
import { IRequest } from 'src/utils/types'
import { CreatePATDto } from './dto/create-pat.dto'
import { PatService } from './pat.service'

@ApiTags('Authentication')
@ApiBearerAuth('Authorization')
@Controller('pats')
export class PatController {
private readonly logger = new Logger(PatController.name)

constructor(private readonly patService: PatService) {}

/**
* Create a PAT
* @param req
* @param dto
* @returns
*/
@ApiOperation({ summary: 'Create a PAT' })
@ApiResponse({ type: ResponseUtil })
@UseGuards(JwtAuthGuard)
@Post()
async create(@Req() req: IRequest, @Body() dto: CreatePATDto) {
const uid = req.user.id
// check max count, 10
const count = await this.patService.count(uid)
if (count >= 10) {
return ResponseUtil.error('Max count of PAT is 10')
}

const pat = await this.patService.create(uid, dto)
return ResponseUtil.ok(pat)
}

/**
* List PATs
* @param req
* @returns
*/
@ApiOperation({ summary: 'List PATs' })
@ApiResponse({ type: ResponseUtil })
@UseGuards(JwtAuthGuard)
@Get()
async findAll(@Req() req: IRequest) {
const uid = req.user.id
const pats = await this.patService.findAll(uid)
return ResponseUtil.ok(pats)
}

/**
* Delete a PAT
* @param req
* @param id
* @returns
*/
@ApiOperation({ summary: 'Delete a PAT' })
@ApiResponse({ type: ResponseUtil })
@UseGuards(JwtAuthGuard)
@Delete(':id')
async remove(@Req() req: IRequest, @Param('id') id: string) {
const uid = req.user.id
const pat = await this.patService.remove(uid, id)
return ResponseUtil.ok(pat)
}
}
66 changes: 66 additions & 0 deletions server/src/user/pat.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Injectable, Logger } from '@nestjs/common'
import { GenerateAlphaNumericPassword } from 'src/utils/random'
import { PrismaService } from '../prisma.service'
import { CreatePATDto } from './dto/create-pat.dto'

@Injectable()
export class PatService {
private readonly logger = new Logger(PatService.name)

constructor(private readonly prisma: PrismaService) {}

async create(userid: string, dto: CreatePATDto) {
const { name, expiresIn } = dto
const token = 'laf_' + GenerateAlphaNumericPassword(60)

const pat = await this.prisma.personalAccessToken.create({
data: {
name,
token,
expiredAt: new Date(Date.now() + expiresIn * 1000),
user: {
connect: { id: userid },
},
},
})
return pat
}

async findAll(userid: string) {
const pats = await this.prisma.personalAccessToken.findMany({
where: { uid: userid },
select: {
id: true,
uid: true,
name: true,
expiredAt: true,
createdAt: true,
},
})
return pats
}

async findOne(token: string) {
const pat = await this.prisma.personalAccessToken.findFirst({
where: { token },
include: {
user: true,
},
})
return pat
}

async count(userid: string) {
const count = await this.prisma.personalAccessToken.count({
where: { uid: userid },
})
return count
}

async remove(userid: string, id: string) {
const pat = await this.prisma.personalAccessToken.deleteMany({
where: { id, uid: userid },
})
return pat
}
}
5 changes: 4 additions & 1 deletion server/src/user/user.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Module } from '@nestjs/common'
import { PrismaService } from '../prisma.service'
import { UserService } from './user.service'
import { PatService } from './pat.service'
import { PatController } from './pat.controller'

@Module({
providers: [UserService, PrismaService],
providers: [UserService, PrismaService, PatService],
exports: [UserService],
controllers: [PatController],
})
export class UserModule {}

0 comments on commit d4da55a

Please sign in to comment.