Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): implement pat schema & apis #597

Merged
merged 1 commit into from
Jan 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {}