@wabot-dev/framework 0.2.0-beta.2 → 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.
- package/dist/src/addon/auth/jwt/Jwt.js +7 -9
- package/dist/src/addon/auth/jwt/JwtRefreshToken.js +40 -35
- package/dist/src/addon/auth/jwt/JwtRefreshTokenRepository.js +6 -0
- package/dist/src/addon/auth/jwt/PgJwtRefreshTokenRepository.js +27 -0
- package/dist/src/index.d.ts +22 -14
- package/package.json +1 -1
|
@@ -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:
|
|
25
|
+
expirationTime: Date.now() + this.config.refreshExpirationSeconds * 1000,
|
|
25
26
|
});
|
|
26
|
-
const
|
|
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:
|
|
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
|
|
39
|
-
|
|
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
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
17
|
-
this.data.
|
|
18
|
-
return
|
|
32
|
+
const secret = `${JwtRefreshToken.PREFIX}${crypto.randomBytes(32).toString('hex')}`;
|
|
33
|
+
this.data.secretHash = JwtRefreshToken.hashSecret(secret);
|
|
34
|
+
return secret;
|
|
19
35
|
}
|
|
20
|
-
|
|
21
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
|
@@ -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(),
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1091,27 +1091,30 @@ declare function jwtConnectionGuard(): (target: object, propertyKey: string | sy
|
|
|
1091
1091
|
declare function jwtGuard(): (target: object, propertyKey: string | symbol) => void;
|
|
1092
1092
|
|
|
1093
1093
|
interface IJwtRefreshTokenData<A extends IStorableData> extends IEntityData {
|
|
1094
|
+
secretHash?: string;
|
|
1095
|
+
metadata?: Record<string, string>;
|
|
1094
1096
|
authInfo: A;
|
|
1095
|
-
passwordHash?: string;
|
|
1096
1097
|
expirationTime: number;
|
|
1098
|
+
revokedAt?: number;
|
|
1097
1099
|
}
|
|
1098
1100
|
declare class JwtRefreshToken<A extends IStorableData> extends Entity<IJwtRefreshTokenData<A>> {
|
|
1101
|
+
static PREFIX: string;
|
|
1102
|
+
static hashSecret(secret: string): string;
|
|
1099
1103
|
get authInfo(): A;
|
|
1104
|
+
get metadata(): Record<string, string>;
|
|
1100
1105
|
get expirationTime(): Date;
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
static deflate(data: {
|
|
1109
|
-
id: string;
|
|
1110
|
-
pass: string;
|
|
1111
|
-
}): 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;
|
|
1112
1113
|
}
|
|
1113
1114
|
|
|
1114
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>[]>;
|
|
1115
1118
|
}
|
|
1116
1119
|
|
|
1117
1120
|
declare class JwtTokenDto {
|
|
@@ -1141,6 +1144,8 @@ declare class JwtSigner {
|
|
|
1141
1144
|
}
|
|
1142
1145
|
|
|
1143
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>;
|
|
1144
1149
|
find(id: string): Promise<JwtRefreshToken<D> | null>;
|
|
1145
1150
|
findOrThrow(id: string): Promise<JwtRefreshToken<D>>;
|
|
1146
1151
|
findByIds(ids: string[]): Promise<JwtRefreshToken<D>[]>;
|
|
@@ -1156,8 +1161,8 @@ declare class Jwt {
|
|
|
1156
1161
|
private jwtRefreshTokenRepository;
|
|
1157
1162
|
private config;
|
|
1158
1163
|
constructor(auth: Auth<any>, jwtSigner: JwtSigner, jwtRefreshTokenRepository: JwtRefreshTokenRepository<any>, config: JwtConfig);
|
|
1159
|
-
createToken(): Promise<JwtAccessAndRefreshTokenDto>;
|
|
1160
|
-
|
|
1164
|
+
createToken(metadata?: Record<string, string>): Promise<JwtAccessAndRefreshTokenDto>;
|
|
1165
|
+
findRefreshTokenAuthInfo(secret: string): Promise<any>;
|
|
1161
1166
|
}
|
|
1162
1167
|
|
|
1163
1168
|
declare class JwtConnectionGuardMiddleware implements IConnectionMiddleware {
|
|
@@ -1176,6 +1181,9 @@ declare class JwtGuardMiddleware implements IMiddleware {
|
|
|
1176
1181
|
|
|
1177
1182
|
declare class PgJwtRefreshTokenRepository<D extends IStorableData> extends PgCrudRepository<JwtRefreshToken<D>> implements IJwtRefreshTokenRepository<D> {
|
|
1178
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>[]>;
|
|
1179
1187
|
}
|
|
1180
1188
|
|
|
1181
1189
|
declare class AnthropicChatAdapter implements IChatAdapter {
|