fix: JWT tokens expire instantly + double /api prefix in analytics
All checks were successful
Test Asgard Runner / test (push) Successful in 3s

- Auth service used flat env var names (JWT_SECRET, JWT_ACCESS_EXPIRY_SECONDS)
  but @nestjs/config nests them under jwt.* — configService.get() returned
  undefined, so expiresIn was 0 and tokens expired on issue (iat === exp)
- JWT strategy had same bug for secretOrKey
- AnalyticsView passed /api/analytics/... to useApi which already prepends /api,
  resulting in /api/api/analytics/... (404)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-02-15 21:40:32 -05:00
parent 2ad6a658ca
commit e849d7803c
3 changed files with 10 additions and 10 deletions

View File

@@ -146,7 +146,7 @@ export class AuthService {
async refresh(refreshToken: string) { async refresh(refreshToken: string) {
try { try {
const payload = await this.jwtService.verifyAsync(refreshToken, { const payload = await this.jwtService.verifyAsync(refreshToken, {
secret: this.configService.get<string>('JWT_SECRET'), secret: this.configService.get<string>('jwt.secret'),
}); });
const user = await this.userRepository.findOne({ const user = await this.userRepository.findOne({
@@ -166,8 +166,8 @@ export class AuthService {
is_super_admin: user.is_super_admin, is_super_admin: user.is_super_admin,
}, },
{ {
secret: this.configService.get<string>('JWT_SECRET'), secret: this.configService.get<string>('jwt.secret'),
expiresIn: this.configService.get<number>('JWT_ACCESS_EXPIRY_SECONDS', 3600), expiresIn: this.configService.get<number>('jwt.accessExpirySeconds') || 900,
}, },
); );
@@ -322,13 +322,13 @@ export class AuthService {
}; };
const accessToken = await this.jwtService.signAsync(payload, { const accessToken = await this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_SECRET'), secret: this.configService.get<string>('jwt.secret'),
expiresIn: this.configService.get<number>('JWT_ACCESS_EXPIRY_SECONDS', 3600), expiresIn: this.configService.get<number>('jwt.accessExpirySeconds') || 900,
}); });
const refreshToken = await this.jwtService.signAsync(payload, { const refreshToken = await this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_SECRET'), secret: this.configService.get<string>('jwt.secret'),
expiresIn: this.configService.get<number>('JWT_REFRESH_EXPIRY_SECONDS', 604800), // 7 days default expiresIn: this.configService.get<number>('jwt.refreshExpirySeconds') || 604800,
}); });
return { return {

View File

@@ -34,7 +34,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
super({ super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false, ignoreExpiration: false,
secretOrKey: configService.get<string>('JWT_SECRET'), secretOrKey: configService.get<string>('jwt.secret'),
}); });
} }

View File

@@ -33,8 +33,8 @@ const loadAnalytics = async () => {
const hours = rangeToHours(timeRange.value) const hours = rangeToHours(timeRange.value)
const [summaryRes, timeseriesRes] = await Promise.all([ const [summaryRes, timeseriesRes] = await Promise.all([
api.get<AnalyticsSummary>(`/api/analytics/summary?range=${hours}`), api.get<AnalyticsSummary>(`/analytics/summary?range=${hours}`),
api.get<TimeseriesData>(`/api/analytics/timeseries?range=${hours}&granularity=hourly`) api.get<TimeseriesData>(`/analytics/timeseries?range=${hours}&granularity=hourly`)
]) ])
summary.value = summaryRes summary.value = summaryRes