@wabot-dev/framework 0.2.0-beta.1 → 0.2.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,10 +33,7 @@ let ApiKeyConnectionGuardMiddleware = class ApiKeyConnectionGuardMiddleware {
33
33
  throw new CustomError({ httpCode: 401, message: 'Token not available' });
34
34
  }
35
35
  try {
36
- const authInfo = await this.apiKeyRepository.findAuthInfoBySecret(keySecret);
37
- if (!authInfo) {
38
- throw new CustomError({ httpCode: 401, message: 'Invalid token' });
39
- }
36
+ const authInfo = await this.apiKeyRepository.findAndValidate(keySecret);
40
37
  this.auth.assign(authInfo);
41
38
  }
42
39
  catch (err) {
@@ -21,10 +21,7 @@ let ApiKeyGuardMiddleware = class ApiKeyGuardMiddleware {
21
21
  throw new CustomError({ httpCode: 401, message: 'Authorization should be an Api-Key' });
22
22
  }
23
23
  try {
24
- const authInfo = await this.apiKeyRepository.findAuthInfoBySecret(keySecret);
25
- if (!authInfo) {
26
- throw new CustomError({ httpCode: 401, message: 'Invalid key' });
27
- }
24
+ const authInfo = await this.apiKeyRepository.findAndValidate(keySecret);
28
25
  this.auth.assign(authInfo);
29
26
  }
30
27
  catch (err) {
@@ -1,20 +1,23 @@
1
1
  class ApiKeyRepository {
2
- findAuthInfoBySecret(secret) {
2
+ find(id) {
3
+ throw new Error('Method not implemented.');
4
+ }
5
+ findOrThrow(id) {
3
6
  throw new Error('Method not implemented.');
4
7
  }
5
8
  findByMetadata(metadata) {
6
9
  throw new Error('Method not implemented.');
7
10
  }
8
- find(id) {
11
+ create(item) {
9
12
  throw new Error('Method not implemented.');
10
13
  }
11
- findOrThrow(id) {
14
+ generate(req) {
12
15
  throw new Error('Method not implemented.');
13
16
  }
14
- create(item) {
17
+ findBySecret(secret) {
15
18
  throw new Error('Method not implemented.');
16
19
  }
17
- generate(req) {
20
+ findAndValidate(secret) {
18
21
  throw new Error('Method not implemented.');
19
22
  }
20
23
  }
@@ -3,6 +3,7 @@ import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
3
3
  import { Pool } from 'pg';
4
4
  import { singleton } from 'tsyringe';
5
5
  import { ApiKey } from './ApiKey.js';
6
+ import { CustomError } from '../../../core/error/CustomError.js';
6
7
 
7
8
  let PgApiKeyRepository = class PgApiKeyRepository extends PgCrudRepository {
8
9
  constructor(pool) {
@@ -12,7 +13,14 @@ let PgApiKeyRepository = class PgApiKeyRepository extends PgCrudRepository {
12
13
  constructor: ApiKey,
13
14
  });
14
15
  }
15
- async findAuthInfoBySecret(secret) {
16
+ async findAndValidate(secret) {
17
+ const apiKey = await this.findBySecret(secret);
18
+ if (!apiKey) {
19
+ throw new CustomError({ message: 'Invalid Api Key', httpCode: 401 });
20
+ }
21
+ return apiKey.authInfo;
22
+ }
23
+ async findBySecret(secret) {
16
24
  const secretHash = ApiKey.hashSecret(secret);
17
25
  const query = `
18
26
  SELECT ${this.columns}
@@ -21,7 +29,7 @@ let PgApiKeyRepository = class PgApiKeyRepository extends PgCrudRepository {
21
29
  LIMIT 1
22
30
  `;
23
31
  const items = await this.query(query, [JSON.stringify({ secretHash })]);
24
- return items[0]?.authInfo ?? null;
32
+ return items[0] ?? null;
25
33
  }
26
34
  async findByMetadata(metadata) {
27
35
  const query = `
@@ -3,42 +3,21 @@ import { CustomError } from '../../../core/error/CustomError.js';
3
3
  class RemoteApiKeyRepository {
4
4
  fetcher;
5
5
  cacheSeconds;
6
- cacheById = new Map();
7
6
  cacheBySecret = new Map();
8
7
  constructor(fetcher, cacheSeconds) {
9
8
  this.fetcher = fetcher;
10
9
  this.cacheSeconds = cacheSeconds;
11
10
  }
12
- async find(id) {
13
- const now = Date.now();
14
- const cached = this.cacheById.get(id);
15
- if (cached && cached.expiresAt > now) {
16
- return cached.value;
17
- }
18
- const result = await this.fetcher.fetchById(id);
19
- this.cacheById.set(id, {
20
- value: result,
21
- expiresAt: now + this.cacheSeconds * 1000,
22
- });
23
- return result;
24
- }
25
- async findOrThrow(id) {
26
- const result = await this.find(id);
27
- if (!result) {
28
- throw new Error(`API key with ID '${id}' not found.`);
29
- }
30
- return result;
31
- }
32
- async findAuthInfoBySecret(secret) {
11
+ async findAndValidate(secret) {
33
12
  const now = Date.now();
34
13
  const cached = this.cacheBySecret.get(secret);
35
14
  if (cached && cached.expiresAt > now) {
36
15
  if (!cached.value) {
37
16
  throw new CustomError({ message: 'Invalid Api Key', httpCode: 401 });
38
17
  }
39
- return cached.value.authInfo;
18
+ return cached.value;
40
19
  }
41
- const result = await this.fetcher.fetchBySecret(secret);
20
+ const result = await this.fetcher.fetchAuthInfoBySecret(secret);
42
21
  this.cacheBySecret.set(secret, {
43
22
  value: result,
44
23
  expiresAt: now + this.cacheSeconds * 1000,
@@ -46,7 +25,16 @@ class RemoteApiKeyRepository {
46
25
  if (!result) {
47
26
  throw new CustomError({ message: 'Invalid Api Key', httpCode: 401 });
48
27
  }
49
- return result.authInfo;
28
+ return result;
29
+ }
30
+ find(id) {
31
+ throw new Error('Method not implemented.');
32
+ }
33
+ findOrThrow(id) {
34
+ throw new Error('Method not implemented.');
35
+ }
36
+ findBySecret(secret) {
37
+ throw new Error('Method not implemented.');
50
38
  }
51
39
  findByMetadata(metadata) {
52
40
  throw new Error('Method not implemented.');
@@ -17,17 +17,18 @@ let Jwt = class Jwt {
17
17
  this.jwtRefreshTokenRepository = jwtRefreshTokenRepository;
18
18
  this.config = config;
19
19
  }
20
- async createToken() {
20
+ async createToken(metadata) {
21
21
  const authInfo = this.auth.require();
22
22
  const refreshToken = new JwtRefreshToken({
23
+ metadata,
23
24
  authInfo,
24
- expirationTime: new Date().getTime() + this.config.refreshExpirationSeconds * 1000,
25
+ expirationTime: Date.now() + this.config.refreshExpirationSeconds * 1000,
25
26
  });
26
- const refreshPassword = refreshToken.generatePassword();
27
+ const refreshSecret = refreshToken.generateSecret();
27
28
  await this.jwtRefreshTokenRepository.create(refreshToken);
28
29
  const access = await this.jwtSigner.signAccessToken(refreshToken);
29
30
  const refresh = {
30
- token: JwtRefreshToken.deflate({ id: refreshToken.id, pass: refreshPassword }),
31
+ token: refreshSecret,
31
32
  expiration: new Date(refreshToken.expirationTime),
32
33
  };
33
34
  return {
@@ -35,11 +36,8 @@ let Jwt = class Jwt {
35
36
  refresh,
36
37
  };
37
38
  }
38
- async refreshToken(refreshSecret) {
39
- const { id, pass } = JwtRefreshToken.inflate(refreshSecret);
40
- const refreshToken = await this.jwtRefreshTokenRepository.findOrThrow(id);
41
- refreshToken.validatePassword(pass);
42
- return this.jwtSigner.signAccessToken(refreshToken);
39
+ async findRefreshTokenAuthInfo(secret) {
40
+ return await this.jwtRefreshTokenRepository.findAndValidate(secret);
43
41
  }
44
42
  };
45
43
  Jwt = __decorate([
@@ -1,55 +1,60 @@
1
1
  import { Entity } from '../../../core/entity/Entity.js';
2
2
  import { CustomError } from '../../../core/error/CustomError.js';
3
- import { Password } from '../../../core/password/Password.js';
3
+ import crypto from 'node:crypto';
4
4
 
5
5
  class JwtRefreshToken extends Entity {
6
+ static PREFIX = 'rt_';
7
+ static hashSecret(secret) {
8
+ return crypto.createHash('sha256').update(secret).digest('hex');
9
+ }
6
10
  get authInfo() {
7
11
  return this.data.authInfo;
8
12
  }
13
+ get metadata() {
14
+ return this.data.metadata ?? {};
15
+ }
9
16
  get expirationTime() {
10
17
  return new Date(this.data.expirationTime);
11
18
  }
12
- generatePassword() {
13
- if (this.data.passwordHash) {
14
- throw new Error('This api key, already has a secret');
19
+ isExpired() {
20
+ return Date.now() > this.data.expirationTime;
21
+ }
22
+ revoke() {
23
+ this.data.revokedAt = Date.now();
24
+ }
25
+ isRevoked() {
26
+ return this.data.revokedAt != null;
27
+ }
28
+ generateSecret() {
29
+ if (this.data.secretHash) {
30
+ throw new Error('This Token key already has a secret');
15
31
  }
16
- const password = Password.generate(64);
17
- this.data.passwordHash = Password.hash({ password: password });
18
- return password;
32
+ const secret = `${JwtRefreshToken.PREFIX}${crypto.randomBytes(32).toString('hex')}`;
33
+ this.data.secretHash = JwtRefreshToken.hashSecret(secret);
34
+ return secret;
19
35
  }
20
- isValidPassword(password) {
21
- if (new Date().getTime() > this.data.expirationTime) {
36
+ isValidSecret(secret) {
37
+ if (!secret.startsWith(JwtRefreshToken.PREFIX))
22
38
  return false;
23
- }
24
- if (!this.data.passwordHash)
39
+ if (!this.data.secretHash)
25
40
  return false;
26
- return Password.isValid({ password: password, hash: this.data.passwordHash });
41
+ const hashed = JwtRefreshToken.hashSecret(secret);
42
+ const stored = this.data.secretHash;
43
+ const hashedBuf = Buffer.from(hashed, 'hex');
44
+ const storedBuf = Buffer.from(stored, 'hex');
45
+ return hashedBuf.length === storedBuf.length && crypto.timingSafeEqual(hashedBuf, storedBuf);
27
46
  }
28
- validatePassword(password) {
29
- if (!this.isValidPassword(password)) {
30
- throw new CustomError({ message: 'Invalid Api key', httpCode: 401 });
31
- }
32
- }
33
- static inflate(secret) {
34
- try {
35
- const json = Buffer.from(secret, 'base64').toString('utf-8');
36
- const data = JSON.parse(json);
37
- if (!data.id || !data.pass) {
38
- throw new Error('invalid secret structure');
39
- }
40
- return data;
41
- }
42
- catch (err) {
43
- throw new Error('fail to inflate secret: ' + err.message);
44
- }
47
+ isValidToken(secret) {
48
+ if (this.isExpired())
49
+ return false;
50
+ if (this.isRevoked())
51
+ return false;
52
+ return this.isValidSecret(secret);
45
53
  }
46
- static deflate(data) {
47
- const { id, pass } = data;
48
- if (!id || !pass) {
49
- throw new Error('id and pass required');
54
+ validateToken(secret) {
55
+ if (!this.isValidToken(secret)) {
56
+ throw new CustomError({ message: 'Invalid Token', httpCode: 401 });
50
57
  }
51
- const json = JSON.stringify({ id, pass });
52
- return Buffer.from(json, 'utf-8').toString('base64');
53
58
  }
54
59
  }
55
60
 
@@ -1,4 +1,10 @@
1
1
  class JwtRefreshTokenRepository {
2
+ findByMetadata(metadata) {
3
+ throw new Error('Method not implemented.');
4
+ }
5
+ findAndValidate(secret) {
6
+ throw new Error('Method not implemented.');
7
+ }
2
8
  find(id) {
3
9
  throw new Error('Method not implemented.');
4
10
  }
@@ -3,6 +3,7 @@ import { singleton } from '../../../core/injection/index.js';
3
3
  import { Pool } from 'pg';
4
4
  import { PgCrudRepository } from '../../../feature/pg/PgCrudRepository.js';
5
5
  import { JwtRefreshToken } from './JwtRefreshToken.js';
6
+ import { CustomError } from '../../../core/error/CustomError.js';
6
7
 
7
8
  let PgJwtRefreshTokenRepository = class PgJwtRefreshTokenRepository extends PgCrudRepository {
8
9
  constructor(pool) {
@@ -12,6 +13,32 @@ let PgJwtRefreshTokenRepository = class PgJwtRefreshTokenRepository extends PgCr
12
13
  constructor: JwtRefreshToken,
13
14
  });
14
15
  }
16
+ async findAndValidate(secret) {
17
+ const apiKey = await this.findBySecret(secret);
18
+ if (!apiKey) {
19
+ throw new CustomError({ message: 'Invalid Token', httpCode: 401 });
20
+ }
21
+ return apiKey.authInfo;
22
+ }
23
+ async findBySecret(secret) {
24
+ const secretHash = JwtRefreshToken.hashSecret(secret);
25
+ const query = `
26
+ SELECT ${this.columns}
27
+ FROM ${this.table}
28
+ WHERE data @> $1::jsonb
29
+ LIMIT 1
30
+ `;
31
+ const items = await this.query(query, [JSON.stringify({ secretHash })]);
32
+ return items[0] ?? null;
33
+ }
34
+ async findByMetadata(metadata) {
35
+ const query = `
36
+ SELECT ${this.columns}
37
+ FROM ${this.table}
38
+ WHERE data @> $1::jsonb
39
+ `;
40
+ return await this.query(query, [JSON.stringify({ metadata })]);
41
+ }
15
42
  };
16
43
  PgJwtRefreshTokenRepository = __decorate([
17
44
  singleton(),
@@ -1024,7 +1024,8 @@ interface IApiKeyRepository<A extends IStorableData> {
1024
1024
  findByMetadata(metadata: Record<string, string>): Promise<ApiKey<A>[]>;
1025
1025
  create(item: ApiKey<A>): Promise<void>;
1026
1026
  generate(req: IGenerateApiKeyReq<A>): Promise<IGenerateApiKeyRes<A>>;
1027
- findAuthInfoBySecret(secret: string): Promise<A | null>;
1027
+ findBySecret(secret: string): Promise<ApiKey<A> | null>;
1028
+ findAndValidate(secret: string): Promise<A>;
1028
1029
  }
1029
1030
  interface IGenerateApiKeyReq<A extends IStorableData> extends IStorableData {
1030
1031
  name: string;
@@ -1037,12 +1038,13 @@ interface IGenerateApiKeyRes<A extends IStorableData> {
1037
1038
  }
1038
1039
 
1039
1040
  declare class ApiKeyRepository<A extends IStorableData> implements IApiKeyRepository<A> {
1040
- findAuthInfoBySecret(secret: string): Promise<A>;
1041
- findByMetadata(metadata: Record<string, string>): Promise<ApiKey<A>[]>;
1042
1041
  find(id: string): Promise<ApiKey<A> | null>;
1043
1042
  findOrThrow(id: string): Promise<ApiKey<A>>;
1043
+ findByMetadata(metadata: Record<string, string>): Promise<ApiKey<A>[]>;
1044
1044
  create(item: ApiKey<A>): Promise<void>;
1045
1045
  generate(req: IGenerateApiKeyReq<A>): Promise<IGenerateApiKeyRes<A>>;
1046
+ findBySecret(secret: string): Promise<ApiKey<A> | null>;
1047
+ findAndValidate(secret: string): Promise<A>;
1046
1048
  }
1047
1049
 
1048
1050
  declare class ApiKeyConnectionGuardMiddleware implements IConnectionMiddleware {
@@ -1061,24 +1063,24 @@ declare class ApiKeyGuardMiddleware implements IMiddleware {
1061
1063
 
1062
1064
  declare class PgApiKeyRepository<A extends IStorableData> extends PgCrudRepository<ApiKey<A>> implements IApiKeyRepository<A> {
1063
1065
  constructor(pool: Pool);
1064
- findAuthInfoBySecret(secret: string): Promise<A | null>;
1066
+ findAndValidate(secret: string): Promise<A>;
1067
+ findBySecret(secret: string): Promise<ApiKey<A> | null>;
1065
1068
  findByMetadata(metadata: Record<string, string>): Promise<ApiKey<A>[]>;
1066
1069
  generate(req: IGenerateApiKeyReq<A>): Promise<IGenerateApiKeyRes<A>>;
1067
1070
  }
1068
1071
 
1069
1072
  interface IRemoteApiKeyFetcher<A extends IStorableData> {
1070
- fetchById: (id: string) => Promise<ApiKey<A> | null>;
1071
- fetchBySecret: (secret: string) => Promise<ApiKey<A> | null>;
1073
+ fetchAuthInfoBySecret: (secret: string) => Promise<A | null>;
1072
1074
  }
1073
1075
  declare class RemoteApiKeyRepository<A extends IStorableData> implements IApiKeyRepository<A> {
1074
1076
  private fetcher;
1075
1077
  private cacheSeconds;
1076
- private cacheById;
1077
1078
  private cacheBySecret;
1078
1079
  constructor(fetcher: IRemoteApiKeyFetcher<A>, cacheSeconds: number);
1080
+ findAndValidate(secret: string): Promise<A>;
1079
1081
  find(id: string): Promise<ApiKey<A> | null>;
1080
1082
  findOrThrow(id: string): Promise<ApiKey<A>>;
1081
- findAuthInfoBySecret(secret: string): Promise<A>;
1083
+ findBySecret(secret: string): Promise<ApiKey<A> | null>;
1082
1084
  findByMetadata(metadata: Record<string, string>): Promise<ApiKey<A>[]>;
1083
1085
  create(item: ApiKey<A>): Promise<void>;
1084
1086
  generate(req: IGenerateApiKeyReq<A>): Promise<IGenerateApiKeyRes<A>>;
@@ -1089,27 +1091,30 @@ declare function jwtConnectionGuard(): (target: object, propertyKey: string | sy
1089
1091
  declare function jwtGuard(): (target: object, propertyKey: string | symbol) => void;
1090
1092
 
1091
1093
  interface IJwtRefreshTokenData<A extends IStorableData> extends IEntityData {
1094
+ secretHash?: string;
1095
+ metadata?: Record<string, string>;
1092
1096
  authInfo: A;
1093
- passwordHash?: string;
1094
1097
  expirationTime: number;
1098
+ revokedAt?: number;
1095
1099
  }
1096
1100
  declare class JwtRefreshToken<A extends IStorableData> extends Entity<IJwtRefreshTokenData<A>> {
1101
+ static PREFIX: string;
1102
+ static hashSecret(secret: string): string;
1097
1103
  get authInfo(): A;
1104
+ get metadata(): Record<string, string>;
1098
1105
  get expirationTime(): Date;
1099
- generatePassword(): string;
1100
- isValidPassword(password: string): boolean;
1101
- validatePassword(password: string): void;
1102
- static inflate(secret: string): {
1103
- id: string;
1104
- pass: string;
1105
- };
1106
- static deflate(data: {
1107
- id: string;
1108
- pass: string;
1109
- }): string;
1106
+ isExpired(): boolean;
1107
+ revoke(): void;
1108
+ isRevoked(): boolean;
1109
+ generateSecret(): string;
1110
+ isValidSecret(secret: string): boolean;
1111
+ isValidToken(secret: string): boolean;
1112
+ validateToken(secret: string): void;
1110
1113
  }
1111
1114
 
1112
1115
  interface IJwtRefreshTokenRepository<D extends IStorableData> extends ICrudRepository<JwtRefreshToken<D>> {
1116
+ findAndValidate(secret: string): Promise<D>;
1117
+ findByMetadata(metadata: Record<string, string>): Promise<JwtRefreshToken<D>[]>;
1113
1118
  }
1114
1119
 
1115
1120
  declare class JwtTokenDto {
@@ -1139,6 +1144,8 @@ declare class JwtSigner {
1139
1144
  }
1140
1145
 
1141
1146
  declare class JwtRefreshTokenRepository<D extends IStorableData> implements IJwtRefreshTokenRepository<D> {
1147
+ findByMetadata(metadata: Record<string, string>): Promise<JwtRefreshToken<D>[]>;
1148
+ findAndValidate(secret: string): Promise<D>;
1142
1149
  find(id: string): Promise<JwtRefreshToken<D> | null>;
1143
1150
  findOrThrow(id: string): Promise<JwtRefreshToken<D>>;
1144
1151
  findByIds(ids: string[]): Promise<JwtRefreshToken<D>[]>;
@@ -1154,8 +1161,8 @@ declare class Jwt {
1154
1161
  private jwtRefreshTokenRepository;
1155
1162
  private config;
1156
1163
  constructor(auth: Auth<any>, jwtSigner: JwtSigner, jwtRefreshTokenRepository: JwtRefreshTokenRepository<any>, config: JwtConfig);
1157
- createToken(): Promise<JwtAccessAndRefreshTokenDto>;
1158
- refreshToken(refreshSecret: string): Promise<JwtTokenDto>;
1164
+ createToken(metadata?: Record<string, string>): Promise<JwtAccessAndRefreshTokenDto>;
1165
+ findRefreshTokenAuthInfo(secret: string): Promise<any>;
1159
1166
  }
1160
1167
 
1161
1168
  declare class JwtConnectionGuardMiddleware implements IConnectionMiddleware {
@@ -1174,6 +1181,9 @@ declare class JwtGuardMiddleware implements IMiddleware {
1174
1181
 
1175
1182
  declare class PgJwtRefreshTokenRepository<D extends IStorableData> extends PgCrudRepository<JwtRefreshToken<D>> implements IJwtRefreshTokenRepository<D> {
1176
1183
  constructor(pool: Pool);
1184
+ findAndValidate(secret: string): Promise<D>;
1185
+ findBySecret(secret: string): Promise<JwtRefreshToken<D> | null>;
1186
+ findByMetadata(metadata: Record<string, string>): Promise<JwtRefreshToken<D>[]>;
1177
1187
  }
1178
1188
 
1179
1189
  declare class AnthropicChatAdapter implements IChatAdapter {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wabot-dev/framework",
3
- "version": "0.2.0-beta.1",
3
+ "version": "0.2.0-beta.3",
4
4
  "description": "Framework for IA Chat Bots",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",