Commit 0df1c6b5 authored by nanahira's avatar nanahira

add /api/novelai-auth

parent df1ebd2a
Pipeline #17332 passed with stages
in 3 minutes and 45 seconds
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
"rxjs": "^6.6.3", "rxjs": "^6.6.3",
"sqlite3": "^5.0.0", "sqlite3": "^5.0.0",
"swagger-ui-express": "^4.1.6", "swagger-ui-express": "^4.1.6",
"typeorm": "^0.2.37", "typeorm": "0.2.45",
"underscore": "^1.12.0" "underscore": "^1.12.0"
}, },
"devDependencies": { "devDependencies": {
......
import { Test, TestingModule } from '@nestjs/testing';
import { AccountService } from './account.service';
describe('AccountService', () => {
let service: AccountService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AccountService],
}).compile();
service = module.get<AccountService>(AccountService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import {
HttpException,
HttpService,
Inject,
Injectable,
InternalServerErrorException,
} from '@nestjs/common';
import { config } from 'src/config';
export interface MycardUser {
id: number;
username: string;
name: string;
email: string;
password_hash: string;
salt: string;
active: boolean;
admin: boolean;
avatar: string;
locale: string;
registration_ip_address: string;
ip_address: string;
created_at: string;
updated_at: string;
}
@Injectable()
export class AccountService {
private url = config.accountsUrl + '/authUser';
constructor(private http: HttpService) {}
async getUserFromToken(token: string): Promise<MycardUser> {
try {
const result = await this.http
.get<MycardUser & { error?: string }>(this.url, {
responseType: 'json',
headers: {
Authorization: `Bearer ${token}`,
},
})
.toPromise();
if (result.status >= 400) {
throw new HttpException(result.data?.error, result.status);
}
return result.data;
} catch (e) {
throw new InternalServerErrorException(e);
}
}
async checkToken(token: string) {
if (typeof token !== 'string') {
throw new HttpException('Invalid token', 400);
}
if (token.startsWith('Bearer ')) {
token = token.slice(7);
}
return this.getUserFromToken(token);
}
}
...@@ -4,31 +4,33 @@ import { ...@@ -4,31 +4,33 @@ import {
Controller, Controller,
ForbiddenException, ForbiddenException,
Get, Get,
Headers,
HttpCode, HttpCode,
HttpException,
InternalServerErrorException, InternalServerErrorException,
NotFoundException, NotFoundException,
Param, Param,
ParseIntPipe, ParseIntPipe,
Post, Post,
Query, Query,
Req,
Res,
UploadedFile, UploadedFile,
UploadedFiles,
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import express from 'express';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { UserInfo } from './entities/mycard/UserInfo';
import { config } from './config'; import { config } from './config';
import { AnyFilesInterceptor, FileInterceptor } from '@nestjs/platform-express'; import { FileInterceptor } from '@nestjs/platform-express';
import { HttpResponseService } from './http-response/http-response.service'; import { HttpResponseService } from './http-response/http-response.service';
import { CodeResponseDto } from './dto/CodeResponse.dto'; import { CodeResponseDto } from './dto/CodeResponse.dto';
import multer from 'multer'; import multer from 'multer';
import cryptoRandomString from 'crypto-random-string'; import cryptoRandomString from 'crypto-random-string';
import { join } from 'path'; import { join } from 'path';
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; import {
ApiBody,
ApiConsumes,
ApiHeader,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { FileUploadDto } from './dto/FileUploadDto'; import { FileUploadDto } from './dto/FileUploadDto';
import { HomePageMatchCountDto } from './dto/HomePageMatchCount.dto'; import { HomePageMatchCountDto } from './dto/HomePageMatchCount.dto';
...@@ -271,4 +273,12 @@ export class AppController { ...@@ -271,4 +273,12 @@ export class AppController {
async getLastMonthBattleCount(): Promise<HomePageMatchCountDto> { async getLastMonthBattleCount(): Promise<HomePageMatchCountDto> {
return this.appService.getLastMonthBattleCount(); return this.appService.getLastMonthBattleCount();
} }
@Get('novelai-auth')
@ApiOperation({ summary: 'novelai 用认证' })
@ApiHeader({ name: 'Authorization' })
@ApiOkResponse({ type: CodeResponseDto })
async novelaiAuth(@Headers('Authorization') token: string) {
return this.appService.novelaiAuth(token);
}
} }
...@@ -52,6 +52,7 @@ import { CardInfoService } from './card-info/card-info.service'; ...@@ -52,6 +52,7 @@ import { CardInfoService } from './card-info/card-info.service';
import { ServeStaticModule } from '@nestjs/serve-static'; import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path'; import { join } from 'path';
import { AthleticCheckerService } from './athletic-checker/athletic-checker.service'; import { AthleticCheckerService } from './athletic-checker/athletic-checker.service';
import { AccountService } from './account/account.service';
const ygoproEntities = [YGOProDatabaseDatas, YGOProDatabaseTexts]; const ygoproEntities = [YGOProDatabaseDatas, YGOProDatabaseTexts];
const mycardEntities = [ const mycardEntities = [
...@@ -138,6 +139,7 @@ const mycardEntities = [ ...@@ -138,6 +139,7 @@ const mycardEntities = [
EloService, EloService,
CardInfoService, CardInfoService,
AthleticCheckerService, AthleticCheckerService,
AccountService,
], ],
}) })
export class AppModule {} export class AppModule {}
import { Injectable, NotFoundException } from '@nestjs/common'; import { HttpException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectConnection, InjectEntityManager } from '@nestjs/typeorm'; import { InjectConnection, InjectEntityManager } from '@nestjs/typeorm';
import { import {
Brackets, Brackets,
...@@ -38,6 +38,8 @@ import { EloService } from './elo/elo.service'; ...@@ -38,6 +38,8 @@ import { EloService } from './elo/elo.service';
import { CardInfoService } from './card-info/card-info.service'; import { CardInfoService } from './card-info/card-info.service';
import { AthleticCheckerService } from './athletic-checker/athletic-checker.service'; import { AthleticCheckerService } from './athletic-checker/athletic-checker.service';
import { HomePageMatchCountDto } from './dto/HomePageMatchCount.dto'; import { HomePageMatchCountDto } from './dto/HomePageMatchCount.dto';
import { AccountService } from './account/account.service';
import { CodeResponseDto } from './dto/CodeResponse.dto';
const attrOffset = 1010; const attrOffset = 1010;
const raceOffset = 1020; const raceOffset = 1020;
...@@ -122,6 +124,7 @@ export class AppService { ...@@ -122,6 +124,7 @@ export class AppService {
private eloService: EloService, private eloService: EloService,
private cardInfoService: CardInfoService, private cardInfoService: CardInfoService,
private athleticCheckerService: AthleticCheckerService, private athleticCheckerService: AthleticCheckerService,
private accountService: AccountService,
) { ) {
this.log.setContext('ygopro-arena-revive'); this.log.setContext('ygopro-arena-revive');
this.chineseDirtyFilter = new Filter({ this.chineseDirtyFilter = new Filter({
...@@ -1777,4 +1780,24 @@ export class AppService { ...@@ -1777,4 +1780,24 @@ export class AppService {
}); });
return new HomePageMatchCountDto(count); return new HomePageMatchCountDto(count);
} }
async novelaiAuth(token: string) {
const user = await this.accountService.checkToken(token);
if (user.admin) {
return new CodeResponseDto(200);
}
return this.mcdb.transaction(async (edb) => {
const userInfo = await edb.getRepository(UserInfo).findOne({
select: ['username', 'exp'],
where: { username: user.username },
});
if (!userInfo || userInfo.exp < config.novelaiCost) {
throw new HttpException(new CodeResponseDto(402), 402);
}
await edb
.getRepository(UserInfo)
.decrement({ username: user.username }, 'exp', config.novelaiCost);
return new CodeResponseDto(200);
});
}
} }
...@@ -8,6 +8,8 @@ export interface Config { ...@@ -8,6 +8,8 @@ export interface Config {
deckIdentifierPath: string; deckIdentifierPath: string;
analyzerHost: string; analyzerHost: string;
enableSchedule: boolean; enableSchedule: boolean;
accountsUrl: string;
novelaiCost: number;
} }
export const athleticCheckConfig = { export const athleticCheckConfig = {
...@@ -31,4 +33,9 @@ export const config: Config = { ...@@ -31,4 +33,9 @@ export const config: Config = {
deckIdentifierPath: process.env.DECK_IDENTIFIER_PATH, deckIdentifierPath: process.env.DECK_IDENTIFIER_PATH,
analyzerHost: process.env.ANALYZER_HOST, analyzerHost: process.env.ANALYZER_HOST,
enableSchedule: !process.env.NO_SCHEDULE, enableSchedule: !process.env.NO_SCHEDULE,
accountsUrl:
process.env.ACCOUNTS_URL || 'https://sapi.moecube.com:444/accounts',
novelaiCost: process.env.NOVELAI_COST
? parseInt(process.env.NOVELAI_COST)
: 3,
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment