Conversation
WalkthroughAdds a self-hosted JWT library and JwtService, a Redis-backed TokenService with token lifecycle and device limits, updates auth flows/modules/tests to use TokenService, introduces config schema and env vars, adds utilities and path mappings, and adjusts frontend types/state for access/refresh token TTLs. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant AuthService
participant TokenService
participant JwtService
participant Redis
Client->>AuthService: POST /login (email,password)
AuthService->>AuthService: validate credentials
AuthService->>TokenService: createToken(userId,email)
TokenService->>JwtService: sign access token (payload + jti, ttl)
TokenService->>JwtService: sign refresh token (payload + jti, ttl)
JwtService-->>TokenService: accessToken, refreshToken
TokenService-->>AuthService: TokenData (tokens, JTIs, TTLs)
AuthService->>TokenService: issueToken(userId, TokenData)
TokenService->>Redis: store tokens (keys, expirations) and append user token list
TokenService->>Redis: enforce DEVICE_LIMIT (revoke oldest if needed)
TokenService-->>AuthService: issued
AuthService-->>Client: LoginResponse {accessToken, accessTokenTTL, refreshToken, refreshTokenTTL}
Note over Client,AuthService: Later - request with token
Client->>AuthService: request (Authorization: Bearer token)
AuthService->>JwtService: decode(token)
AuthService->>TokenService: accessTokenAlive(token)
TokenService->>Redis: exists(accessTokenJTI)
Redis-->>TokenService: boolean
TokenService-->>AuthService: alive? true/false
alt alive
AuthService-->>Client: allow
else
AuthService-->>Client: deny (unauthorized)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
template/tinyvue/src/store/modules/user/types.ts (1)
26-31: AlignUserInfo.role(Role[]) with store usage to avoid type/shape mismatchChanging
roletoRole[]matches the backend shape, but the store still treatsroleas a string intemplate/tinyvue/src/store/modules/user/index.ts(e.g.,switchRoles()toggles'user'/'admin', and theloginaction buildsuserInfowithrole: ''then assigns a role name string). This creates a type and runtime mismatch betweenUserInfoand the actual store state.Consider either:
- Updating the store to genuinely store
Role[](and derive any display role name/job fromrole[0]), or- Temporarily widening the type to cover both shapes during migration, e.g.:
-export interface UserInfo { +export interface UserInfo { ... - role: Role[]; + role: Role[] | RoleType; ... }and then cleaning up the string-based usages.
template/tinyvue/src/store/modules/user/index.ts (1)
36-46: Fixrolestate shape: array default conflicts with string-based handlingWith
UserInfo.rolenow typed asRole[], settingrole: []in state is fine, but other logic still treatsroleas a string:
switchRoles()(Line 55–60) doesthis.role === 'user' ? 'admin' : 'user', soroleflips between strings, notRole[].- In
login(), the constructeduserInfoliteral usesrole: '', later assigninguserInfo.role = userRes.data.role[0].name;(again, a string), while also readinguserRes.data.roleas an array.This leads to
rolehaving incompatible shapes at runtime (sometimes array, sometimes string) and contradicts the updated type.You likely want one of:
Store
Role[]in state
- Initialize
rolefrom the backend array, e.g.role: userRes.data.role ?? [].- Keep role name/job derived from
role[0]only injobor a separateprimaryRoleNamefield.- Remove or rewrite
switchRoles()to work on Role objects (or drop it if it was only a demo toggle).Keep
roleas a string and add a separateroles: Role[]
- Revert
UserInfo.roleto the string union and introduceroles?: Role[]for the backend data.- Use
roleswhen you need full role objects and keeprolefor display/permission checks.Right now, this inconsistency will make both typing and runtime behavior brittle.
Also applies to: 55-61, 90-119
🧹 Nitpick comments (19)
template/nestJs/src/permission/__tests__/permission.guard.spec.ts (1)
8-10: Use a partial mock to avoid breaking otheruuidexports in this test fileThe deterministic
v7mock is good for stable tests, but replacing the entireuuidmodule with onlyv7can break cases where SUT (or future code) imports otheruuidexports (e.g.,v4, default). A safer pattern is to extend the real module and override justv7:-jest.mock('uuid', () => ({ - v7: jest.fn(() => 'mocked-uuid-v7'), -})); +jest.mock('uuid', () => ({ + ...jest.requireActual('uuid'), + v7: jest.fn(() => 'mocked-uuid-v7'), +}));This keeps the behavior of all other exports intact while still making
v7deterministic in this test.template/nestJs/libs/redis/redis.service.ts (1)
13-15: LGTM! Exposing the Redis client is appropriate for complex operations inTokenService.Consider adding an explicit return type for consistency with the codebase's type annotations:
- getRedis(){ + getRedis(): Redis { return this.redisClient; }template/nestJs/src/user/user.service.ts (1)
375-376: Role comparison may produce false positives.The comparison
userRoles !== (roleIds || []).join('')relies on role ID ordering. If the same roles are provided in a different order, this will incorrectly trigger a kickout.Consider using a set-based comparison:
- const userRoles = user.role.map((role) => role.id).join(''); + const userRoleIds = new Set(user.role.map((role) => role.id)); + const newRoleIds = new Set(roleIds || []); ... - if (userRoles !== (roleIds || []).join('')) { + const rolesChanged = userRoleIds.size !== newRoleIds.size || + [...userRoleIds].some(id => !newRoleIds.has(id)); + if (rolesChanged) { await this.authService.kickOut(user.id); }template/nestJs/libs/jwt/src/jwt.service.ts (1)
15-19: Redundant variable assignment.The
retvariable is unnecessary:async verify<T>(token: string): Promise<JWTVerifyResult<T>>{ - const secrect = new TextEncoder().encode(this.cfg.secrect); - const ret = await jwtVerify<T>(token,secrect); - return ret + const secret = new TextEncoder().encode(this.cfg.secret); + return jwtVerify<T>(token, secret); }template/nestJs/src/auth/dto/login-in.dto.ts (1)
1-6: Consider adding validation decorators and readonly modifiers.While this is a response DTO (output), adding
class-validatordecorators (e.g.,@IsString(),@IsNumber()) can help catch serialization issues early. Additionally, marking fields asreadonlysignals immutability.Example:
+import { IsString, IsNumber } from 'class-validator'; + export class LoginResponse { + @IsString() + readonly accessToken: string; + + @IsNumber() + readonly accessTokenTTL: number; + + @IsString() + readonly refreshToken: string; + + @IsNumber() + readonly refreshTokenTTL: number; - accessToken: string; - accessTokenTTL: number; - refreshToken: string; - refreshTokenTTL: number; }template/nestJs/libs/jwt/src/jwt.module.ts (1)
5-9: LGTM! Standard configurable module pattern.The module correctly extends
ConfigurableModuleClassand exportsJwtServicefor dependency injection. The emptyimportsarray on line 6 could be omitted as it's the default, but this is a minor stylistic choice.template/nestJs/libs/utils/pick.ts (1)
1-16: LGTM! Type-safe object picking utility.The implementation is correct and type-safe. The early return for falsy objects is a good defensive practice.
Optional: Consider using
hasOwnPropertyinstead of theinoperator to avoid picking inherited properties:for (const key of keys) { - if (key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { result[key] = obj[key]; } }This prevents unintended behavior if
objhas inherited properties matching the requested keys.template/nestJs/src/user/__tests__/user.service.spec.ts (1)
163-164: Consider using a more specific assertion.Other tests use
toHaveBeenCalledWith(1)for thekickOutcall. For consistency, consider updating this assertion to match.- expect(authService.kickOut).toHaveBeenCalled() + expect(authService.kickOut).toHaveBeenCalledWith(1)template/nestJs/src/auth/auth.module.ts (1)
31-41: Inconsistent configuration pattern:process.envvsConfigService.
SelfJwtModuleusesConfigServiceforAUTH_SECRET(line 27), but the existingJwtModuleusesprocess.env.AUTH_SECRETdirectly (line 34). Consider usingConfigServiceconsistently for both modules to benefit from validation and type safety.JwtModule.registerAsync({ - imports: [RedisModule], - useFactory: async () => ({ - secret: process.env.AUTH_SECRET, + imports: [RedisModule, ConfigModule], + inject: [ConfigService], + useFactory: async (cfg: ConfigService) => ({ + secret: cfg.get('AUTH_SECRET'), global: true, signOptions: { - expiresIn: process.env.EXPIRES_IN, + expiresIn: cfg.get('EXPIRES_IN'), }, }), global: true, }),template/nestJs/src/auth/__tests__/auth.guard.spec.ts (1)
206-227: Consider adding a test for whenaccessTokenAlivereturnsfalse.The tests cover
accessTokenAlivereturningtrue, but there's no explicit test for whenaccessTokenAlivereturnsfalsefor a non-API token. This would improve coverage of the token validation branch inauth.guard.ts.it('should throw HttpException when access token is not alive', async () => { reflector.getAllAndOverride.mockReturnValue(false); jwt.verify.mockResolvedValue({}); jwt.decode.mockReturnValue({ email: 'test@example.com' }); tokenService.accessTokenAlive.mockResolvedValue(false); const mockRequest = { headers: { authorization: 'Bearer validToken', }, }; const mockContext = { getHandler: jest.fn(), getClass: jest.fn(), switchToHttp: () => ({ getRequest: () => mockRequest, }), } as any; await expect(authGuard.canActivate(mockContext)).rejects.toThrow(HttpException); });template/nestJs/src/auth/auth.guard.ts (2)
44-74: Error handling masks the original exception.The outer
catchblock at line 76 catches all exceptions (including theHttpExceptionthrown at lines 57-62 and 67-72) and re-throws a generic "tokenExpire" error. This masks the original exception and its context.Consider re-throwing
HttpExceptioninstances directly:} catch (err) { + if (err instanceof HttpException) { + throw err; + } throw new HttpException( i18n.t('exception.common.tokenExpire', { lang: I18nContext.current().lang, }), HttpStatus.UNAUTHORIZED ); }
66-66: Minor formatting: add space afterawait.- if (!await this.tokenService.accessTokenAlive(token)){ + if (!(await this.tokenService.accessTokenAlive(token))) {template/nestJs/src/auth/__tests__/auth.service.spec.ts (2)
67-72: ConfigService mock lacks return values forget()calls.The mock's
getfunction returnsundefinedfor all keys. IfAuthService.login()usesConfigService.get()for values likeDEVICE_LIMITorREDIS_SECONDS, tests may not reflect realistic behavior.{ provide: ConfigService, useValue:{ - get: jest.fn() + get: jest.fn((key: string) => { + const config = { + REDIS_SECONDS: 7200, + REFRESH_TOKEN_TTL: 604800000, + DEVICE_LIMIT: 5, + }; + return config[key]; + }) } }
143-156: Test could verify the returned token data structure.The test only asserts that
tokenService.createTokenwas called, but doesn't verify whatservice.login()returns. Consider asserting the response contains expected token fields.- await service.login({ email: 'test@example.com', password: 'hashed-password' }); + const result = await service.login({ email: 'test@example.com', password: 'hashed-password' }); expect(tokenService.createToken).toHaveBeenCalledWith(1, 'test@example.com'); + // Optionally verify response structure + expect(result).toBeDefined();template/nestJs/src/config-schema.ts (1)
5-5: Consider usingnumbertype for port fields.
DATABASE_PORTandREDIS_PORTare typed asstringbut typically used as numbers. This may cause issues when passing to connection libraries.- DATABASE_PORT:string; + DATABASE_PORT:number; ... - REDIS_PORT:string; + REDIS_PORT:number;And update the schema accordingly:
- DATABASE_PORT: Joi.string(), + DATABASE_PORT: Joi.number(), ... - REDIS_PORT: Joi.string(), + REDIS_PORT: Joi.number(),Also applies to: 14-14
template/nestJs/src/auth/token.service.ts (1)
36-61:revokeExpiredTokenhas O(n) Redis round-trips - consider pipelining.Each token check issues a separate
redis.exists()call. For users with many tokens, this creates significant latency.Use Redis pipeline or mget to batch existence checks:
async revokeExpiredToken(uid: number){ const redis = this.redisService.getRedis(); - const allRefreshTokenJti = await redis.lrange(`user:${uid}:rt`, 0, -1); const allAccessTokenJti = await redis.lrange(`user:${uid}:at`, 0, -1); - const expiredRefreshTokenJti = []; - const expiredAccessTokenJti = []; - for (const refreshTokenJti of allRefreshTokenJti) { - if (await redis.exists(`rt:${uid}:${refreshTokenJti}`)) { - continue; - } - expiredRefreshTokenJti.push(refreshTokenJti); - } - for (const accessTokenJti of allAccessTokenJti) { - if (await redis.exists(`at:${uid}:${accessTokenJti}`)) { - continue; - } - expiredAccessTokenJti.push(accessTokenJti); - } + + // Pipeline existence checks + const pipeline = redis.pipeline(); + allRefreshTokenJti.forEach(jti => pipeline.exists(`rt:${uid}:${jti}`)); + allAccessTokenJti.forEach(jti => pipeline.exists(`at:${uid}:${jti}`)); + const results = await pipeline.exec(); + + const rtResults = results.slice(0, allRefreshTokenJti.length); + const atResults = results.slice(allRefreshTokenJti.length); + + const expiredRefreshTokenJti = allRefreshTokenJti.filter((_, i) => !rtResults[i][1]); + const expiredAccessTokenJti = allAccessTokenJti.filter((_, i) => !atResults[i][1]); + + // Batch removals + const cleanupPipeline = redis.pipeline(); for (const accessJti of expiredAccessTokenJti) { - await redis.lrem(`user:${uid}:at`, 0, accessJti); + cleanupPipeline.lrem(`user:${uid}:at`, 0, accessJti); } - for (const refresJti of expiredRefreshTokenJti) { - await redis.lrem(`user:${uid}:rt`, 0, refresJti); + for (const refreshJti of expiredRefreshTokenJti) { + cleanupPipeline.lrem(`user:${uid}:rt`, 0, refreshJti); } + await cleanupPipeline.exec(); }template/nestJs/src/auth/auth.service.ts (2)
105-106: Use ConfigService consistently for configuration access.Line 105 uses
process.env.API_TOKEN_SECONDSdirectly while the rest of the file usesConfigService. This bypasses validation and type safety. Consider addingAPI_TOKEN_SECONDSto theConfigureschema and accessing it viathis.cfg.get().- const ttl = parseInt(process.env.API_TOKEN_SECONDS) || 86400 * 7; // 默认7天 + const ttl = this.cfg.get('API_TOKEN_SECONDS', { infer: true }) ?? 86400 * 7; // 默认7天Note: This requires adding
API_TOKEN_SECONDSto theConfiguretype insrc/config-schema.ts.
100-102: Replace deprecatedsubstrwithsubstring.The
substrmethod is deprecated. Usesubstringorsliceinstead.- `api_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + `api_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;template/tinyvue/src/api/user.ts (1)
25-30: LoginResponse typing is correct; consider clarifying/co-locating legacy LoginResThe new
LoginResponseinterface andlogin()return type line up with the NestJSLoginResponseDTO (access/refresh tokens plus TTLs) and the updated store usage ofres.data.accessToken.
LoginResnow only backsloginMailandlogout. To reduce confusion around “two login response shapes”, consider:
- Renaming
LoginResto something endpoint-specific (e.g.,MailLoginResponse), and/or- Adjusting the
logoutgeneric if/auth/logoutno longer returns{ token, userInfo }.Functionally the new typing for
/auth/loginlooks good; just ensure all callers ofloginhave been updated away fromtokentoaccessToken.Also applies to: 48-50
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (29)
template/nestJs/.env.example(1 hunks)template/nestJs/libs/jwt/src/index.ts(1 hunks)template/nestJs/libs/jwt/src/jwt.configure.ts(1 hunks)template/nestJs/libs/jwt/src/jwt.module.ts(1 hunks)template/nestJs/libs/jwt/src/jwt.service.ts(1 hunks)template/nestJs/libs/jwt/tsconfig.lib.json(1 hunks)template/nestJs/libs/redis/redis.service.ts(1 hunks)template/nestJs/libs/utils/inex.ts(1 hunks)template/nestJs/libs/utils/pick.ts(1 hunks)template/nestJs/nest-cli.json(2 hunks)template/nestJs/package.json(5 hunks)template/nestJs/src/app.module.ts(2 hunks)template/nestJs/src/auth/__tests__/auth.guard.spec.ts(5 hunks)template/nestJs/src/auth/__tests__/auth.service.spec.ts(6 hunks)template/nestJs/src/auth/auth.guard.ts(3 hunks)template/nestJs/src/auth/auth.module.ts(1 hunks)template/nestJs/src/auth/auth.service.ts(4 hunks)template/nestJs/src/auth/dto/login-in.dto.ts(1 hunks)template/nestJs/src/auth/entity/token.ts(1 hunks)template/nestJs/src/auth/token.service.ts(1 hunks)template/nestJs/src/config-schema.ts(1 hunks)template/nestJs/src/permission/__tests__/permission.guard.spec.ts(2 hunks)template/nestJs/src/user/__tests__/user.service.spec.ts(6 hunks)template/nestJs/src/user/user.module.ts(1 hunks)template/nestJs/src/user/user.service.ts(5 hunks)template/nestJs/tsconfig.json(1 hunks)template/tinyvue/src/api/user.ts(2 hunks)template/tinyvue/src/store/modules/user/index.ts(2 hunks)template/tinyvue/src/store/modules/user/types.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
template/nestJs/src/auth/dto/login-in.dto.ts (1)
template/tinyvue/src/api/user.ts (1)
LoginResponse(25-30)
template/nestJs/libs/jwt/src/jwt.module.ts (1)
template/nestJs/src/auth/auth.module.ts (1)
Module(14-45)
template/nestJs/src/user/user.module.ts (1)
template/nestJs/src/auth/auth.module.ts (1)
Module(14-45)
template/tinyvue/src/api/user.ts (1)
template/nestJs/src/auth/dto/login-in.dto.ts (1)
LoginResponse(1-6)
template/nestJs/libs/jwt/src/jwt.service.ts (4)
template/nestJs/src/auth/auth.guard.ts (1)
Injectable(17-89)template/nestJs/src/auth/auth.service.ts (1)
Injectable(16-125)template/nestJs/src/auth/token.service.ts (1)
Injectable(19-185)template/nestJs/libs/jwt/src/jwt.configure.ts (1)
JwtOptions(3-5)
template/nestJs/src/app.module.ts (1)
template/nestJs/src/config-schema.ts (1)
CONFIG_SCHEMA(24-43)
template/nestJs/src/auth/auth.guard.ts (1)
template/nestJs/src/auth/entity/token.ts (1)
TokenPayload(5-5)
template/nestJs/src/auth/token.service.ts (3)
template/nestJs/src/auth/entity/token.ts (2)
AccessTokenPayload(11-15)RefreshTokenPayload(16-20)template/nestJs/libs/jwt/src/jwt.service.ts (1)
Injectable(5-35)template/nestJs/libs/redis/redis.service.ts (1)
Injectable(4-71)
template/nestJs/src/auth/auth.module.ts (3)
template/nestJs/libs/jwt/src/jwt.module.ts (1)
Module(5-10)template/nestJs/src/app.module.ts (1)
Module(42-235)template/nestJs/src/user/user.module.ts (1)
Module(10-16)
template/nestJs/src/auth/auth.service.ts (3)
template/nestJs/src/config-schema.ts (1)
Configure(3-22)template/nestJs/src/auth/entity/token.ts (1)
AccessTokenPayload(11-15)template/nestJs/libs/utils/pick.ts (1)
pick(1-16)
🪛 dotenv-linter (4.0.0)
template/nestJs/.env.example
[warning] 17-17: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 19-19: [UnorderedKey] The DEVICE_LIMIT key should go before the EXPIRES_IN key
(UnorderedKey)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: e2e-test
🔇 Additional comments (30)
template/nestJs/src/user/user.service.ts (4)
44-44: Whitespace-only change.No functional impact.
253-253: LGTM! Correctly updated to useuser.idto align with the newTokenService.revokeByUid(id: number)signature.
297-297: LGTM! Consistent with the new id-based token revocation.
312-312: LGTM! Consistent with the new id-based token revocation.template/nestJs/libs/jwt/src/jwt.service.ts (3)
11-13:decode()can throw on malformed tokens.
decodeJwtthrows if the token is malformed. Consider whether callers handle this or if a try-catch should be added here for consistent error handling.
21-34: LGTM! The sign implementation correctly sets expiration and uses HS256 with the encoded secret.
1-9: Typo:secrectshould besecret.The configuration property
secrectis misspelled. This affects the public API surface acrossjwt.configure.tsand consumers. Fix the typo injwt.configure.ts:export interface JwtOptions { - secrect: string; + secret: string; }Then update references here:
- const secrect = new TextEncoder().encode(this.cfg.secrect); + const secret = new TextEncoder().encode(this.cfg.secret);Likely an incorrect or invalid review comment.
template/nestJs/src/app.module.ts (2)
40-40: LGTM! Good practice to import and use a centralized configuration schema.
51-55: Unable to complete verification due to repository access failure.The repository clone operation failed, preventing access to the codebase and the CONFIG_SCHEMA file referenced in the verification request. Without being able to inspect the actual schema configuration, I cannot verify whether critical environment variables (AUTH_SECRET, DATABASE_*) are properly marked as required as noted in the original review comment.
template/nestJs/src/user/user.module.ts (1)
8-14: I attempted to verify the review comment by examining the repository directly, but unfortunately encountered a repository access failure. Without access to the actual codebase, I cannot definitively confirm:
- Whether
AuthModulecurrently exportsTokenService,AuthService, andRedisService- Whether a circular dependency actually exists between
AuthModuleandUserModule- Whether the suggested refactoring would introduce or resolve any issues
- Whether this duplicate provider pattern is intentional or present elsewhere
Manual verification is needed to confirm the following before applying this refactor:
- Check
template/nestJs/src/auth/auth.module.tsto verify it exports the mentioned services- Check if
AuthModuleimportsUserModule(which would create a circular dependency ifUserModulethen importsAuthModule)- Verify there are no initialization order dependencies that require the current structure
The suggestion to import
AuthModuleinstead of duplicating providers is a sound NestJS architectural practice, but the circular dependency concern must be validated before implementation.template/nestJs/libs/jwt/src/index.ts (1)
1-2: LGTM! Clean barrel export pattern.The re-exports follow standard NestJS library conventions, enabling clean imports via the
@app/jwtalias.template/nestJs/tsconfig.json (1)
28-32: LGTM! Path alias correctly configured.The
@app/jwtpath mapping properly points to the new JWT library and follows the existing pattern for other library aliases.template/nestJs/libs/jwt/tsconfig.lib.json (1)
1-9: LGTM! Standard TypeScript library configuration.The configuration properly extends the base tsconfig and sets up the JWT library with appropriate includes/excludes for a NestJS library module.
template/nestJs/libs/jwt/src/jwt.module.ts (1)
1-10: I'll help you verify the configuration interface spelling. Let me search for the "secrect" typo in the JWT-related files.
<function_calls>
#!/bin/bashSearch for "secrect" typo in JWT-related files
rg -n "secrect" --type=ts template/libs/jwt/
</function_calls>Let me also check if the jwt directory structure matches what we're looking for:
<function_calls>
#!/bin/bashFind JWT-related files
fd -t f "jwt" --type ts . | head -20
</function_calls>Let me try a broader search:
<function_calls>
#!/bin/bashSearch for "secrect" across entire repository
rg -n "secrect" --type=ts
</function_calls>Now let me examine the jwt.configure.ts file and auth.module.ts to understand the structure:
<function_calls>
#!/bin/bashFind jwt.configure.ts
fd "jwt.configure" --type ts
</function_calls><function_calls>
#!/bin/bashFind auth.module.ts
fd "auth.module" --type ts
</function_calls><function_calls>
#!/bin/bashGet a list of all TypeScript files in the template directory
find template -name "*.ts" -type f 2>/dev/null | grep -E "(jwt|auth)" | head -20
</function_calls>template/nestJs/libs/utils/inex.ts (1)
1-1: I apologize, but I'm unable to access the repository to verify the filename concern due to connectivity issues. Since I cannot execute the verification scripts or access the codebase to check:
- Whether the file actually exists at
template/nestJs/libs/utils/inex.ts- Whether there are any imports referencing this filename
- Project-specific naming conventions that might justify this name
I'll provide the rewritten review comment based on the original concern, but marked for manual verification:
Verify filename: "inex.ts" may be a typo for "index.ts"
The filename "inex.ts" appears to be a typo. Standard convention for barrel export files is "index.ts". If intentional, this could cause confusion and make the module harder to discover. Confirm whether this name is deliberate or should be corrected to "index.ts", and check whether any imports reference this file.
template/nestJs/src/user/__tests__/user.service.spec.ts (1)
11-13: LGTM!The UUID mock ensures deterministic test behavior for token generation, which is good practice for unit tests.
template/nestJs/nest-cli.json (1)
34-42: LGTM!The new
jwtlibrary project follows the same structure as existingdbandmodelslibraries, maintaining consistency in the monorepo configuration.template/nestJs/src/auth/__tests__/auth.guard.spec.ts (1)
29-37: LGTM!The
tokenServicemock provides comprehensive coverage ofTokenServicemethods. TheaccessTokenAlivemock is correctly used in the valid token test case (line 211).template/nestJs/src/auth/auth.guard.ts (1)
50-50: Good use of runtime type guard.The
'type' in payloadcheck ensures safe property access before comparingpayload.type, preventing potential runtime errors when the payload structure differs.template/nestJs/src/auth/entity/token.ts (2)
1-20: LGTM for the type structure.The token payload types are well-structured with a clear base type and proper cross-referencing between access and refresh tokens via their JTIs.
5-5: Repository access unavailable for verification.The repository clone operation failed, preventing me from verifying the usage patterns of
TokenPayloadandRefreshTokenPayloadthroughout the codebase. Without access to the actual code, I cannot definitively determine whetherRefreshTokenPayloadshould be included in the union type or assess the impact of this suggested change.To properly verify this concern, manual inspection of:
- All usages of
TokenPayloadtype- How
RefreshTokenPayloadis handled in the codebase- Whether refresh tokens are decoded/verified in contexts expecting
TokenPayloadwould be needed.
template/nestJs/package.json (2)
11-11: LGTM for the build/test configuration updates.The format script now includes
libs/**/*.ts,coverageDirectorypath correction, and moduleNameMapper additions for@app/jwtare appropriate for the new library structure.Also applies to: 109-109, 112-113
38-38: The review comment is incorrect—joi version 18.0.2 exists and is currently the latest stable release.The original review claimed that "joi library's latest major version is 17.x" and "Version 18.0.2 does not exist on npm," but verification confirms that joi 18.0.2 is the current latest stable release as of November 2025. The code in the PR is correct and requires no changes. The suggested downgrade to ^17.13.3 would introduce an older version unnecessarily.
template/nestJs/src/auth/__tests__/auth.service.spec.ts (1)
12-14: LGTM for test module setup with TokenService integration.The uuid mock ensures deterministic test behavior, and the tokenService mock covers all necessary lifecycle methods.
Also applies to: 33-41, 63-66
template/nestJs/src/auth/token.service.ts (2)
83-120: LGTM for the token creation logic.The
createTokenmethod properly generates linked access and refresh tokens with cross-referenced JTIs, enabling proper token pair revocation.
63-81: LGTM forrevokeByUidusing Redis multi transaction.Bulk token revocation correctly uses a transaction to atomically delete all user tokens.
template/nestJs/src/auth/auth.service.ts (3)
62-68: LGTM!The login flow correctly builds the payload with user ID and email, creates the token via
TokenService, and returns the structured token response with access/refresh tokens and their TTLs.
97-97: I'll help you verify whether the TTL unit is correct forjwtService.sign. Let me search the codebase to find the JWT service implementation and its expected parameters.
<function_calls>
#!/bin/bashFind JWT-related service files
fd -t f -i 'jwt' --exec basename {} ;
</function_calls>Now let me locate the auth service and JWT service files more specifically:
<function_calls>
#!/bin/bashFind the auth service file and any jwt service files
fd -t f 'auth.service.ts'
echo "---"
fd -t f 'jwt.service.ts'
echo "---"
fd -t f -i 'jwt' -type f
</function_calls>Let me check the auth service file to see the context around line 97:
<function_calls>
#!/bin/bashFirst, let's find the auth service file
find . -name 'auth.service.ts' -type f 2>/dev/null | head -5
</function_calls><function_calls>
#!/bin/bashLet's search for the line with jwtService.sign to find the auth.service.ts
rg -n "jwtService.sign" --type ts
</function_calls><function_calls>
#!/bin/bashSearch for JwtService and its sign method
rg -n "sign(" --type ts | grep -i jwt
</function_calls><function_calls>
#!/bin/bashLet's look at the repository structure first
ls -la template/ 2>/dev/null || ls -la | head -20
</function_calls><function_calls>
#!/bin/bashCheck if template/nestJs/src/auth/auth.service.ts exists
cat template/nestJs/src/auth/auth.service.ts 2>/dev/null | head -120
</function_calls>
32-34: Verify all callers ofkickOutare updated for the new signature.The method signature changed from
kickOut(email: string)tokickOut(id: number). Ensure all call sites throughout the codebase have been updated to pass the user ID instead of email.template/tinyvue/src/store/modules/user/index.ts (1)
85-90: UsingaccessTokenfrom LoginResponse in login flow looks correctThe change to:
const res = await userLogin(loginForm); const { accessToken } = res.data; setToken(accessToken);matches the new
LoginResponsetyping and the PR’s breaking-change description (frontend should store theaccessTokenfrom/auth/login). No issues here, assumingsetTokenis meant for the short‑lived access token.
| PAGINATION_LIMIT = 10 | ||
| GLOBAL_PREFIX = '/' | ||
| MOCK_REGEX = '/mock' | ||
| REFRESH_TOKEN_TTL = 604800000 |
There was a problem hiding this comment.
Fix spacing inconsistency around the equals sign.
Line 17 has spaces around the equals sign (REFRESH_TOKEN_TTL = 604800000), while line 19 follows the standard .env convention without spaces (DEVICE_LIMIT=1). Maintain consistency with the rest of the file.
Apply this diff to fix the spacing:
-REFRESH_TOKEN_TTL = 604800000
+REFRESH_TOKEN_TTL=604800000📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| REFRESH_TOKEN_TTL = 604800000 | |
| REFRESH_TOKEN_TTL=604800000 |
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 17-17: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
🤖 Prompt for AI Agents
In template/nestJs/.env.example around line 17, the assignment uses spaces
around the equals sign ("REFRESH_TOKEN_TTL = 604800000") which is inconsistent
with the rest of the file; change it to remove spaces so it reads
"REFRESH_TOKEN_TTL=604800000" to match the standard .env formatting.
| export interface JwtOptions { | ||
| secrect: string; | ||
| } |
There was a problem hiding this comment.
Typo: secrect should be secret.
This typo will propagate to all consumers of JwtOptions, including the auth.module.ts where secrect: cfg.get('AUTH_SECRET') is used.
export interface JwtOptions {
- secrect: string;
+ secret: string;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export interface JwtOptions { | |
| secrect: string; | |
| } | |
| export interface JwtOptions { | |
| secret: string; | |
| } |
🤖 Prompt for AI Agents
In template/nestJs/libs/jwt/src/jwt.configure.ts around lines 3 to 5, the
JwtOptions interface misspells the property name as `secrect`; rename it to
`secret` and update all consumers (e.g., auth.module.ts and any config calls) to
use `secret: cfg.get('AUTH_SECRET')` (or the appropriate config key) so the
correct property name is used consistently across the codebase.
| "test:e2e": "echo 'No e2e tests implemented yet.'" | ||
| }, | ||
| "dependencies": { | ||
| "@gaonengwww/jose": "^6.1.0", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check npm for package info
curl -s https://registry.npmjs.org/@gaonengwww/jose | jq '{name: .name, "dist-tags": .["dist-tags"], time: .time.modified, maintainers: .maintainers}'Repository: opentiny/tiny-pro
Length of output: 178
🏁 Script executed:
#!/bin/bash
# Check npm for package info
curl -s https://registry.npmjs.org/@gaonengwww/jose | jq '{name: .name, "dist-tags": .["dist-tags"], time: .time.modified, maintainers: .maintainers}'Repository: opentiny/tiny-pro
Length of output: 178
🌐 Web query:
@gaonengwww/jose npm package
💡 Result:
I couldn't find a package named "@gaonengwww/jose" on the public npm registry. The widely used official JOSE library on npm is "jose" (by panva). Install with npm i jose. [1][2]
Sources:
- jose package on npm (official): [1]
- npm docs — about scopes (scoped package naming): [2]
The @gaonengwww/jose package does not exist on the npm registry.
Web search confirms this package is not available publicly. The official JOSE library for Node.js is jose (by panva). Using a non-existent package will cause npm install to fail. If JWT operations are needed, use the official jose package instead, or verify the correct scoped package name and maintainer.
🤖 Prompt for AI Agents
In template/nestJs/package.json at line 24, the dependency "@gaonengwww/jose"
does not exist on npm; replace it with the official "jose" package (e.g. "jose":
"^4") or the correct scoped name if you have a verified private package, then
run npm install and update any imports/usages in the codebase to import from
"jose" instead of the non-existent package.
| SelfJwtModule.registerAsync({ | ||
| isGlobal: true, | ||
| imports: [ConfigModule], | ||
| inject: [ConfigService], | ||
| useFactory(cfg: ConfigService){ | ||
| return { | ||
| secrect: cfg.get('AUTH_SECRET') | ||
| } | ||
| } | ||
| }), |
There was a problem hiding this comment.
Typo propagation: secrect should be secret.
This matches the typo in jwt.configure.ts. Once that's fixed, update this usage as well.
useFactory(cfg: ConfigService){
return {
- secrect: cfg.get('AUTH_SECRET')
+ secret: cfg.get('AUTH_SECRET')
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| SelfJwtModule.registerAsync({ | |
| isGlobal: true, | |
| imports: [ConfigModule], | |
| inject: [ConfigService], | |
| useFactory(cfg: ConfigService){ | |
| return { | |
| secrect: cfg.get('AUTH_SECRET') | |
| } | |
| } | |
| }), | |
| SelfJwtModule.registerAsync({ | |
| isGlobal: true, | |
| imports: [ConfigModule], | |
| inject: [ConfigService], | |
| useFactory(cfg: ConfigService){ | |
| return { | |
| secret: cfg.get('AUTH_SECRET') | |
| } | |
| } | |
| }), |
🤖 Prompt for AI Agents
In template/nestJs/src/auth/auth.module.ts around lines 21 to 30, the factory
return uses the misspelled key "secrect"; change it to "secret" to match
jwt.configure.ts and the expected JwtModule options, and ensure the value comes
from cfg.get('AUTH_SECRET') (adjust types if necessary so the ConfigService
value is accepted).
| async logout(token: string): Promise<void> { | ||
| //通过token解析email | ||
| const decoded = await this.jwtService.verify(token); | ||
| //退出登录后,将token从Redis删除 | ||
| await this.redisService.delUserToken(`user:${decoded.email}:token`); | ||
| const decoded = await this.jwtService.verify<AccessTokenPayload>(token); | ||
| await this.tokenService.revokeByUid(decoded.payload.id) | ||
| return; | ||
| } |
There was a problem hiding this comment.
Add error handling for token verification failure.
If jwtService.verify() fails (e.g., token is expired, malformed, or invalid), the exception will bubble up unhandled. Consider wrapping this in a try-catch to return an appropriate HTTP 401 response.
async logout(token: string): Promise<void> {
- const decoded = await this.jwtService.verify<AccessTokenPayload>(token);
- await this.tokenService.revokeByUid(decoded.payload.id)
+ try {
+ const decoded = await this.jwtService.verify<AccessTokenPayload>(token);
+ await this.tokenService.revokeByUid(decoded.payload.id);
+ } catch {
+ throw new HttpException(
+ this.i18n.translate('exception.auth.invalidToken', {
+ lang: I18nContext.current().lang,
+ }),
+ HttpStatus.UNAUTHORIZED
+ );
+ }
return;
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In template/nestJs/src/auth/auth.service.ts around lines 36–40, wrap the call to
this.jwtService.verify(...) in a try-catch so token verification failures are
handled; on failure throw a NestJS UnauthorizedException with a clear message
(e.g., 'Invalid or expired token'), and only call
this.tokenService.revokeByUid(...) when verification succeeds (use the decoded
payload id). Ensure the function returns void as before and update imports if
necessary to include UnauthorizedException.
| jti: accessTokenJTI, | ||
| id, | ||
| email, | ||
| issueAt: new Date().toLocaleDateString(), |
There was a problem hiding this comment.
toLocaleDateString() produces locale-dependent, imprecise timestamps.
Using toLocaleDateString() for issueAt loses time precision and varies by server locale. Use ISO 8601 format for consistency.
- issueAt: new Date().toLocaleDateString(),
+ issueAt: new Date().toISOString(),Also applies to: 104-104
🤖 Prompt for AI Agents
In template/nestJs/src/auth/token.service.ts around lines 93 and 104, replace
the locale-dependent and imprecise new Date().toLocaleDateString() assignments
for issueAt with an ISO 8601 timestamp (e.g., new Date().toISOString()) so the
value includes full date and time in a consistent, timezone-independent format;
update both occurrences to use toISOString().
| ){ | ||
| const redis = this.redisService.getRedis(); | ||
| let userTokenCount = await this.getUserTokenCount(uid); | ||
| const limit = Number.parseInt(this.cfg.get<string>('DEVICE_LIMIT')); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add radix parameter to parseInt.
Number.parseInt without a radix may interpret strings with leading zeros as octal in some contexts.
- const limit = Number.parseInt(this.cfg.get<string>('DEVICE_LIMIT'));
+ const limit = Number.parseInt(this.cfg.get<string>('DEVICE_LIMIT'), 10);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const limit = Number.parseInt(this.cfg.get<string>('DEVICE_LIMIT')); | |
| const limit = Number.parseInt(this.cfg.get<string>('DEVICE_LIMIT'), 10); |
🤖 Prompt for AI Agents
In template/nestJs/src/auth/token.service.ts around line 154, the call to
Number.parseInt lacks a radix which can lead to ambiguous parsing (e.g., strings
with leading zeros). Change the parsing to include an explicit radix (use 10) so
the line parses DEVICE_LIMIT as a base-10 integer; also consider defaulting or
validating the result (e.g., fallback value or throw) if parseInt returns NaN.
| while ( | ||
| userTokenCount >= limit | ||
| ) { | ||
| const lastToken = await this.getLastToken(uid); | ||
| if (!lastToken) { | ||
| break; | ||
| } | ||
| await this.revokeToken(lastToken.accessToken) | ||
| userTokenCount -= 1; | ||
| } |
There was a problem hiding this comment.
Potential infinite loop if revokeToken fails silently.
The while loop decrements userTokenCount manually after revokeToken, but if revokeToken fails (e.g., Redis error) without throwing, the count won't reflect reality, and getLastToken may return the same token repeatedly.
Consider adding error handling or re-querying the actual count:
while (
userTokenCount >= limit
) {
const lastToken = await this.getLastToken(uid);
if (!lastToken) {
break;
}
await this.revokeToken(lastToken.accessToken)
- userTokenCount -= 1;
+ userTokenCount = await this.getUserTokenCount(uid);
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In template/nestJs/src/auth/token.service.ts around lines 158 to 167, the loop
can hang if revokeToken fails silently because userTokenCount is decremented
locally while the underlying store is unchanged; wrap the revokeToken call in a
try/catch and on failure either log and rethrow (so the outer flow can fail) or
break, and after a successful revoke re-query the authoritative token count
(e.g., await this.getTokenCount(uid)) instead of only decrementing the local
counter; additionally add a small safety guard (max iterations) to ensure the
loop cannot run indefinitely if the store does not change.
| const multi = redis.multi(); | ||
| await multi | ||
| .set(`rt:${uid}:${token.refreshTokenJTI}`, token.refreshToken) | ||
| .set(`at:${uid}:${token.accessTokenJTI}`, token.accessToken) | ||
| .pexpire(`rt:${uid}:${token.refreshTokenJTI}`, token.refreshTokenTTL) | ||
| .pexpire(`at:${uid}:${token.accessTokenJTI}`, token.accessTokenTTL) | ||
| .lpush(`user:${uid}:rt`, token.refreshTokenJTI) | ||
| .lpush(`user:${uid}:at`, token.accessTokenJTI) | ||
| .exec(); |
There was a problem hiding this comment.
Incorrect usage of Redis multi transaction - await before exec() is misplaced.
The await on line 170 is applied before the chained operations, but multi() returns a chainable object that should only be awaited at exec(). This pattern is correct syntactically but stylistically confusing. However, there's a more significant issue: the user:${uid}:rt and user:${uid}:at lists have no TTL set, causing unbounded growth of the JTI lists even after tokens expire.
The token lists will grow indefinitely. Consider setting TTL on the lists or cleaning them up periodically:
const multi = redis.multi();
- await multi
+ multi
.set(`rt:${uid}:${token.refreshTokenJTI}`, token.refreshToken)
.set(`at:${uid}:${token.accessTokenJTI}`, token.accessToken)
.pexpire(`rt:${uid}:${token.refreshTokenJTI}`, token.refreshTokenTTL)
.pexpire(`at:${uid}:${token.accessTokenJTI}`, token.accessTokenTTL)
.lpush(`user:${uid}:rt`, token.refreshTokenJTI)
.lpush(`user:${uid}:at`, token.accessTokenJTI)
- .exec();
+ await multi.exec();Additionally, consider adding TTL to the list keys or implementing periodic cleanup.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In template/nestJs/src/auth/token.service.ts around lines 169-177, the Redis
multi usage and list key management are problematic: remove the premature await
on the multi chain and instead build the chain then await multi.exec(), and
ensure you handle exec() errors/results; also prevent unbounded growth of the
user JTI lists by applying TTLs to the list keys (pexpire `user:${uid}:rt` and
`user:${uid}:at`) or add cleanup logic (e.g., ltrim after pushes or a periodic
job to remove expired JTIs) so the lists don’t grow indefinitely.
| export const CONFIG_SCHEMA = Joi.object<Configure>({ | ||
| DATABASE_HOST: Joi.string(), | ||
| DATABASE_PORT: Joi.string(), | ||
| DATABASE_USERNAME: Joi.string(), | ||
| DATABASE_PASSWORD: Joi.string(), | ||
| DATABASE_NAME: Joi.string(), | ||
| DATABASE_SYNCHRONIZE: Joi.bool(), | ||
| DATABASE_AUTOLOADENTITIES: Joi.bool(), | ||
| AUTH_SECRET: Joi.string(), | ||
| REDIS_SECONDS: Joi.number(), | ||
| REDIS_HOST: Joi.string(), | ||
| REDIS_PORT: Joi.string(), | ||
| EXPIRES_IN: Joi.string(), | ||
| PAGINATION_PAGE: Joi.number(), | ||
| PAGINATION_LIMIT: Joi.number(), | ||
| GLOBAL_PREFIX: Joi.string(), | ||
| MOCK_REGEX: Joi.string(), | ||
| REFRESH_TOKEN_TTL: Joi.number(), | ||
| DEVICE_LIMIT: Joi.number() | ||
| }) |
There was a problem hiding this comment.
Add .required() to critical configuration fields.
Security-sensitive fields like AUTH_SECRET and database connection fields should be required to prevent silent failures with undefined configuration. The app may start with missing credentials and fail at runtime.
export const CONFIG_SCHEMA = Joi.object<Configure>({
- DATABASE_HOST: Joi.string(),
- DATABASE_PORT: Joi.string(),
- DATABASE_USERNAME: Joi.string(),
- DATABASE_PASSWORD: Joi.string(),
- DATABASE_NAME: Joi.string(),
+ DATABASE_HOST: Joi.string().required(),
+ DATABASE_PORT: Joi.string().required(),
+ DATABASE_USERNAME: Joi.string().required(),
+ DATABASE_PASSWORD: Joi.string().required(),
+ DATABASE_NAME: Joi.string().required(),
DATABASE_SYNCHRONIZE: Joi.bool(),
DATABASE_AUTOLOADENTITIES: Joi.bool(),
- AUTH_SECRET: Joi.string(),
+ AUTH_SECRET: Joi.string().required(),
REDIS_SECONDS: Joi.number(),
- REDIS_HOST: Joi.string(),
- REDIS_PORT: Joi.string(),
+ REDIS_HOST: Joi.string().required(),
+ REDIS_PORT: Joi.string().required(),
EXPIRES_IN: Joi.string(),
PAGINATION_PAGE: Joi.number(),
PAGINATION_LIMIT: Joi.number(),
GLOBAL_PREFIX: Joi.string(),
MOCK_REGEX: Joi.string(),
- REFRESH_TOKEN_TTL: Joi.number(),
- DEVICE_LIMIT: Joi.number()
+ REFRESH_TOKEN_TTL: Joi.number().required(),
+ DEVICE_LIMIT: Joi.number().required().min(-1)
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const CONFIG_SCHEMA = Joi.object<Configure>({ | |
| DATABASE_HOST: Joi.string(), | |
| DATABASE_PORT: Joi.string(), | |
| DATABASE_USERNAME: Joi.string(), | |
| DATABASE_PASSWORD: Joi.string(), | |
| DATABASE_NAME: Joi.string(), | |
| DATABASE_SYNCHRONIZE: Joi.bool(), | |
| DATABASE_AUTOLOADENTITIES: Joi.bool(), | |
| AUTH_SECRET: Joi.string(), | |
| REDIS_SECONDS: Joi.number(), | |
| REDIS_HOST: Joi.string(), | |
| REDIS_PORT: Joi.string(), | |
| EXPIRES_IN: Joi.string(), | |
| PAGINATION_PAGE: Joi.number(), | |
| PAGINATION_LIMIT: Joi.number(), | |
| GLOBAL_PREFIX: Joi.string(), | |
| MOCK_REGEX: Joi.string(), | |
| REFRESH_TOKEN_TTL: Joi.number(), | |
| DEVICE_LIMIT: Joi.number() | |
| }) | |
| export const CONFIG_SCHEMA = Joi.object<Configure>({ | |
| DATABASE_HOST: Joi.string().required(), | |
| DATABASE_PORT: Joi.string().required(), | |
| DATABASE_USERNAME: Joi.string().required(), | |
| DATABASE_PASSWORD: Joi.string().required(), | |
| DATABASE_NAME: Joi.string().required(), | |
| DATABASE_SYNCHRONIZE: Joi.bool(), | |
| DATABASE_AUTOLOADENTITIES: Joi.bool(), | |
| AUTH_SECRET: Joi.string().required(), | |
| REDIS_SECONDS: Joi.number(), | |
| REDIS_HOST: Joi.string().required(), | |
| REDIS_PORT: Joi.string().required(), | |
| EXPIRES_IN: Joi.string(), | |
| PAGINATION_PAGE: Joi.number(), | |
| PAGINATION_LIMIT: Joi.number(), | |
| GLOBAL_PREFIX: Joi.string(), | |
| MOCK_REGEX: Joi.string(), | |
| REFRESH_TOKEN_TTL: Joi.number().required(), | |
| DEVICE_LIMIT: Joi.number().required().min(-1) | |
| }) |
🤖 Prompt for AI Agents
In template/nestJs/src/config-schema.ts around lines 24 to 43, several critical
config keys (e.g., DATABASE_HOST, DATABASE_PORT, DATABASE_USERNAME,
DATABASE_PASSWORD, DATABASE_NAME, AUTH_SECRET, and any TTL/credential values
like REFRESH_TOKEN_TTL) are optional in the Joi schema; update the schema by
appending .required() to those sensitive and connection-related fields so the
app fails fast on missing configuration, leaving non-critical keys optional as
needed and ensuring types remain consistent.
|
@GaoNeng-wWw 可能跟 |
|
|
|
uuid 版本改成 9.0 之后, -import { v7 } from "uuid";
+import { v4 as v7 } from "uuid";以上修改之后可以登录了 |
The merge-base changed after approval.
|
@GaoNeng-wWw 无法登录,报错信息: 我尝试在 jwt.service.ts 文件的 sign 方法打印日志,发现点击登录时,会打印两次日志,第一次 expire 和传递给 setExpirationTime 方法的参数都是正常的,第二次 expire 是 undefined,导致传递给 setExpirationTime 方法的参数是 Invalid Date。 |
我待会看下 |
|
好像是环境变量的问题,少了这两个环境变量导致的,我没有更新 |
加上后还会出现这个问题吗 |

PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: #135
What is the new behavior?
允许用户通过设置后端的
DEVICE_LIMIT环境变量来修改同时在线的设备数. 如果设置为-1则不做数量限制.允许多人共用演示
1_crf.mp4
删除账号后吊销所有会话
2_crf.mp4
Does this PR introduce a breaking change?
同时做了双token, 前端需要修改
web/src/store/modules/user/index.tsconst useUserStore = defineStore('user', { async login(loginForm: LoginData) { try { const res = await userLogin(loginForm); - const { token } = res.data; - setToken(token); + const { accessToken } = res.data; + setToken(accessToken);Other information
Summary by CodeRabbit
New Features
Configuration
Frontend
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.