Repository: strapi/strapi
File: packages/core/core/src/services/session-manager.ts (TypeScript)

Code before:

      if (new Date(session.expiresAt) <= new Date()) {
        // Clean up expired session
        await this.provider.deleteBySessionId(payload.sessionId);
        return { isValid: false };
      }

      if (session.userId !== payload.userId) {
        return { isValid: false };
      }

      return {
        isValid: true,
        userId: payload.userId,
        sessionId: payload.sessionId,
      };
    } catch (error: any) {
      // If the token is expired, verify signature ignoring expiration to safely extract payload
      // and clean up the corresponding session in database.
      if (error instanceof jwt.JsonWebTokenError) {
        if (error.name === 'TokenExpiredError') {
          try {
            const expiredPayload = jwt.verify(token, this.config.jwtSecret, {
              // Validate signature but ignore exp to retrieve session information
              ignoreExpiration: true as any,
            }) as RefreshTokenPayload;

            if (expiredPayload?.sessionId) {
              await this.provider.deleteBySessionId(expiredPayload.sessionId);
            }
          } catch (_) {
            // If we cannot recover payload safely, skip cleanup
          }
        }
        return { isValid: false };
      }

      throw error;
    }
  }

  async generateAccessToken(refreshToken: string): Promise<{ token: string } | { error: string }> {
    const validation = await this.validateRefreshToken(refreshToken);

    if (!validation.isValid) {
      return { error: 'invalid_refresh_token' };
    }

    const payload: Omit<AccessTokenPayload, 'iat' | 'exp'> = {
      userId: validation.userId!,
      sessionId: validation.sessionId!,

Review comment (suggestion):
Instead of `as` can we add type checking in case `verify` returns a string? (even though we know it shouldn't with how we're using it)

Code after:
        return { isValid: false };
      }

      const session = await this.provider.findBySessionId(payload.sessionId);
      if (!session) {
        return { isValid: false };
      }

      if (new Date(session.expiresAt) <= new Date()) {
        // Clean up expired session
        await this.provider.deleteBySessionId(payload.sessionId);
        return { isValid: false };
      }

      if (session.userId !== payload.userId) {
        return { isValid: false };
      }

      return {
        isValid: true,
        userId: payload.userId,
        sessionId: payload.sessionId,
      };
    } catch (error: any) {
      // If the token is expired, verify signature ignoring expiration to safely extract payload
      // and clean up the corresponding session in database.
      if (error instanceof jwt.JsonWebTokenError) {
        if (error.name === 'TokenExpiredError') {
          try {
            const verifyResult = jwt.verify(token, this.config.jwtSecret, {
              // Validate signature but ignore exp to retrieve session information
              ignoreExpiration: true,
              algorithms: [this.config.algorithm ?? 'HS256'],
            });

            // Type guard to ensure we have an object payload, not a string
            if (typeof verifyResult === 'string') {
              return { isValid: false };
            }

            const expiredPayload = verifyResult as RefreshTokenPayload;
            if (expiredPayload?.sessionId) {
              await this.provider.deleteBySessionId(expiredPayload.sessionId);
            }
          } catch {
            // If we cannot recover payload safely, skip cleanup
          }
        }
        return { isValid: false };
      }