A social knowledge tool for researchers built on ATProto
1import jwt from 'jsonwebtoken';
2import { v4 as uuidv4 } from 'uuid';
3import { Result, ok, err } from 'src/shared/core/Result';
4import { ITokenService } from '../../application/services/ITokenService';
5import { ITokenRepository } from '../../domain/repositories/ITokenRepository';
6import { TokenPair } from '@semble/types';
7import { EnvironmentConfigService } from 'src/shared/infrastructure/config/EnvironmentConfigService';
8
9export class FakeJwtTokenService implements ITokenService {
10 private jwtSecret: string;
11 private accessTokenExpiresIn: number =
12 new EnvironmentConfigService().getAuthConfig().accessTokenExpiresIn || 3600; // 1 hour
13 private refreshTokenExpiresIn: number =
14 new EnvironmentConfigService().getAuthConfig().refreshTokenExpiresIn ||
15 2592000; // 30 days
16
17 constructor(private tokenRepository: ITokenRepository) {
18 this.jwtSecret = process.env.MOCK_ACCESS_TOKEN || 'mock-access-token-123';
19 }
20
21 async generateToken(did: string): Promise<Result<TokenPair>> {
22 try {
23 // Generate actual JWT access token
24 const accessToken = jwt.sign(
25 { did, iat: Math.floor(Date.now() / 1000) },
26 this.jwtSecret,
27 { expiresIn: this.accessTokenExpiresIn },
28 );
29
30 // Generate refresh token
31 const refreshToken = uuidv4();
32 const tokenId = uuidv4();
33 const now = new Date();
34 const expiresAt = new Date(
35 now.getTime() + this.refreshTokenExpiresIn * 1000,
36 );
37
38 // Store refresh token
39 const saveResult = await this.tokenRepository.saveRefreshToken({
40 tokenId,
41 userDid: did,
42 refreshToken,
43 issuedAt: now,
44 expiresAt,
45 revoked: false,
46 });
47
48 if (saveResult.isErr()) {
49 return err(saveResult.error);
50 }
51
52 return ok({
53 accessToken,
54 refreshToken,
55 expiresIn: this.accessTokenExpiresIn,
56 });
57 } catch (error: any) {
58 return err(error);
59 }
60 }
61
62 async validateToken(token: string): Promise<Result<string | null>> {
63 try {
64 const decoded = jwt.verify(token, this.jwtSecret) as { did: string };
65 return ok(decoded.did);
66 } catch (error) {
67 return ok(null); // Token is invalid or expired
68 }
69 }
70
71 async refreshToken(refreshToken: string): Promise<Result<TokenPair | null>> {
72 try {
73 // Find the refresh token
74 const findResult =
75 await this.tokenRepository.findRefreshToken(refreshToken);
76
77 if (findResult.isErr()) {
78 return err(findResult.error);
79 }
80
81 const tokenData = findResult.unwrap();
82 if (!tokenData) {
83 return ok(null);
84 }
85
86 // Check if token is expired
87 if (new Date() > tokenData.expiresAt) {
88 await this.revokeToken(refreshToken);
89 return ok(null);
90 }
91
92 // Generate new tokens
93 const newTokens = await this.generateToken(tokenData.userDid);
94
95 // Revoke old token
96 await this.revokeToken(refreshToken);
97
98 return newTokens;
99 } catch (error: any) {
100 return err(error);
101 }
102 }
103
104 async revokeToken(refreshToken: string): Promise<Result<void>> {
105 try {
106 const revokeResult =
107 await this.tokenRepository.revokeRefreshToken(refreshToken);
108
109 if (revokeResult.isErr()) {
110 return err(revokeResult.error);
111 }
112
113 return ok(undefined);
114 } catch (error: any) {
115 return err(error);
116 }
117 }
118}