@hamak/auth 0.5.1
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/README.md +366 -0
- package/dist/api/api/auth-service.d.ts +111 -0
- package/dist/api/api/auth-service.d.ts.map +1 -0
- package/dist/api/api/auth-service.js +5 -0
- package/dist/api/api/index.d.ts +2 -0
- package/dist/api/api/index.d.ts.map +1 -0
- package/dist/api/api/index.js +1 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +12 -0
- package/dist/api/tokens/index.d.ts +2 -0
- package/dist/api/tokens/index.d.ts.map +1 -0
- package/dist/api/tokens/index.js +1 -0
- package/dist/api/tokens/service-tokens.d.ts +26 -0
- package/dist/api/tokens/service-tokens.d.ts.map +1 -0
- package/dist/api/tokens/service-tokens.js +25 -0
- package/dist/api/types/auth-result.d.ts +69 -0
- package/dist/api/types/auth-result.d.ts.map +1 -0
- package/dist/api/types/auth-result.js +5 -0
- package/dist/api/types/config.d.ts +130 -0
- package/dist/api/types/config.d.ts.map +1 -0
- package/dist/api/types/config.js +5 -0
- package/dist/api/types/credentials.d.ts +52 -0
- package/dist/api/types/credentials.d.ts.map +1 -0
- package/dist/api/types/credentials.js +5 -0
- package/dist/api/types/index.d.ts +5 -0
- package/dist/api/types/index.d.ts.map +1 -0
- package/dist/api/types/index.js +4 -0
- package/dist/api/types/user.d.ts +39 -0
- package/dist/api/types/user.d.ts.map +1 -0
- package/dist/api/types/user.js +5 -0
- package/dist/impl/index.d.ts +15 -0
- package/dist/impl/index.d.ts.map +1 -0
- package/dist/impl/index.js +21 -0
- package/dist/impl/plugin/auth-plugin-factory.d.ts +20 -0
- package/dist/impl/plugin/auth-plugin-factory.d.ts.map +1 -0
- package/dist/impl/plugin/auth-plugin-factory.js +226 -0
- package/dist/impl/plugin/index.d.ts +2 -0
- package/dist/impl/plugin/index.d.ts.map +1 -0
- package/dist/impl/plugin/index.js +1 -0
- package/dist/impl/services/AuthService.d.ts +44 -0
- package/dist/impl/services/AuthService.d.ts.map +1 -0
- package/dist/impl/services/AuthService.js +277 -0
- package/dist/impl/services/index.d.ts +2 -0
- package/dist/impl/services/index.d.ts.map +1 -0
- package/dist/impl/services/index.js +1 -0
- package/dist/impl/storage/LocalTokenStorage.d.ts +32 -0
- package/dist/impl/storage/LocalTokenStorage.d.ts.map +1 -0
- package/dist/impl/storage/LocalTokenStorage.js +148 -0
- package/dist/impl/storage/MemoryTokenStorage.d.ts +34 -0
- package/dist/impl/storage/MemoryTokenStorage.d.ts.map +1 -0
- package/dist/impl/storage/MemoryTokenStorage.js +91 -0
- package/dist/impl/storage/SessionTokenStorage.d.ts +33 -0
- package/dist/impl/storage/SessionTokenStorage.d.ts.map +1 -0
- package/dist/impl/storage/SessionTokenStorage.js +147 -0
- package/dist/impl/storage/index.d.ts +10 -0
- package/dist/impl/storage/index.d.ts.map +1 -0
- package/dist/impl/storage/index.js +26 -0
- package/dist/impl/store/auth-reducer.d.ts +135 -0
- package/dist/impl/store/auth-reducer.d.ts.map +1 -0
- package/dist/impl/store/auth-reducer.js +179 -0
- package/dist/impl/store/index.d.ts +2 -0
- package/dist/impl/store/index.d.ts.map +1 -0
- package/dist/impl/store/index.js +1 -0
- package/dist/impl/strategies/KeycloakStrategy.d.ts +42 -0
- package/dist/impl/strategies/KeycloakStrategy.d.ts.map +1 -0
- package/dist/impl/strategies/KeycloakStrategy.js +237 -0
- package/dist/impl/strategies/OAuth2Strategy.d.ts +30 -0
- package/dist/impl/strategies/OAuth2Strategy.d.ts.map +1 -0
- package/dist/impl/strategies/OAuth2Strategy.js +232 -0
- package/dist/impl/strategies/PasswordStrategy.d.ts +25 -0
- package/dist/impl/strategies/PasswordStrategy.d.ts.map +1 -0
- package/dist/impl/strategies/PasswordStrategy.js +159 -0
- package/dist/impl/strategies/StrategyRegistry.d.ts +24 -0
- package/dist/impl/strategies/StrategyRegistry.d.ts.map +1 -0
- package/dist/impl/strategies/StrategyRegistry.js +70 -0
- package/dist/impl/strategies/index.d.ts +5 -0
- package/dist/impl/strategies/index.d.ts.map +1 -0
- package/dist/impl/strategies/index.js +4 -0
- package/dist/impl/utils/index.d.ts +3 -0
- package/dist/impl/utils/index.d.ts.map +1 -0
- package/dist/impl/utils/index.js +2 -0
- package/dist/impl/utils/jwt.d.ts +81 -0
- package/dist/impl/utils/jwt.d.ts.map +1 -0
- package/dist/impl/utils/jwt.js +103 -0
- package/dist/impl/utils/pkce.d.ts +44 -0
- package/dist/impl/utils/pkce.d.ts.map +1 -0
- package/dist/impl/utils/pkce.js +93 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/spi/guards/AuthGuard.d.ts +108 -0
- package/dist/spi/guards/AuthGuard.d.ts.map +1 -0
- package/dist/spi/guards/AuthGuard.js +5 -0
- package/dist/spi/guards/index.d.ts +2 -0
- package/dist/spi/guards/index.d.ts.map +1 -0
- package/dist/spi/guards/index.js +1 -0
- package/dist/spi/index.d.ts +12 -0
- package/dist/spi/index.d.ts.map +1 -0
- package/dist/spi/index.js +15 -0
- package/dist/spi/storage/ITokenStorage.d.ts +107 -0
- package/dist/spi/storage/ITokenStorage.d.ts.map +1 -0
- package/dist/spi/storage/ITokenStorage.js +5 -0
- package/dist/spi/storage/index.d.ts +2 -0
- package/dist/spi/storage/index.d.ts.map +1 -0
- package/dist/spi/storage/index.js +1 -0
- package/dist/spi/strategies/IAuthStrategy.d.ts +114 -0
- package/dist/spi/strategies/IAuthStrategy.d.ts.map +1 -0
- package/dist/spi/strategies/IAuthStrategy.js +16 -0
- package/dist/spi/strategies/IStrategyRegistry.d.ts +64 -0
- package/dist/spi/strategies/IStrategyRegistry.d.ts.map +1 -0
- package/dist/spi/strategies/IStrategyRegistry.js +5 -0
- package/dist/spi/strategies/index.d.ts +3 -0
- package/dist/spi/strategies/index.d.ts.map +1 -0
- package/dist/spi/strategies/index.js +2 -0
- package/package.json +78 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy Registry Implementation
|
|
3
|
+
* Registry for managing multiple authentication strategies
|
|
4
|
+
*/
|
|
5
|
+
import type { IAuthStrategy, IOAuthStrategy, IStrategyRegistry } from '../../spi';
|
|
6
|
+
/**
|
|
7
|
+
* Default implementation of the strategy registry
|
|
8
|
+
*/
|
|
9
|
+
export declare class StrategyRegistry implements IStrategyRegistry {
|
|
10
|
+
private strategies;
|
|
11
|
+
private defaultStrategyName;
|
|
12
|
+
register(strategy: IAuthStrategy): void;
|
|
13
|
+
unregister(name: string): void;
|
|
14
|
+
get(name: string): IAuthStrategy | undefined;
|
|
15
|
+
getOAuth(name: string): IOAuthStrategy | undefined;
|
|
16
|
+
getAll(): IAuthStrategy[];
|
|
17
|
+
getAllOAuth(): IOAuthStrategy[];
|
|
18
|
+
has(name: string): boolean;
|
|
19
|
+
getDefault(): IAuthStrategy | undefined;
|
|
20
|
+
setDefault(name: string): void;
|
|
21
|
+
clear(): void;
|
|
22
|
+
private isOAuthStrategy;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=StrategyRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StrategyRegistry.d.ts","sourceRoot":"","sources":["../../../src/impl/strategies/StrategyRegistry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAElF;;GAEG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,mBAAmB,CAAuB;IAElD,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAYvC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAW9B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAQlD,MAAM,IAAI,aAAa,EAAE;IAIzB,WAAW,IAAI,cAAc,EAAE;IAI/B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,UAAU,IAAI,aAAa,GAAG,SAAS;IAOvC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAO9B,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,eAAe;CAGxB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy Registry Implementation
|
|
3
|
+
* Registry for managing multiple authentication strategies
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Default implementation of the strategy registry
|
|
7
|
+
*/
|
|
8
|
+
export class StrategyRegistry {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.strategies = new Map();
|
|
11
|
+
this.defaultStrategyName = null;
|
|
12
|
+
}
|
|
13
|
+
register(strategy) {
|
|
14
|
+
if (this.strategies.has(strategy.name)) {
|
|
15
|
+
console.warn(`Strategy "${strategy.name}" is already registered. Overwriting.`);
|
|
16
|
+
}
|
|
17
|
+
this.strategies.set(strategy.name, strategy);
|
|
18
|
+
// Set as default if it's the first strategy
|
|
19
|
+
if (this.strategies.size === 1) {
|
|
20
|
+
this.defaultStrategyName = strategy.name;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
unregister(name) {
|
|
24
|
+
this.strategies.delete(name);
|
|
25
|
+
// Clear default if it was the unregistered strategy
|
|
26
|
+
if (this.defaultStrategyName === name) {
|
|
27
|
+
this.defaultStrategyName = this.strategies.size > 0
|
|
28
|
+
? this.strategies.keys().next().value ?? null
|
|
29
|
+
: null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
get(name) {
|
|
33
|
+
return this.strategies.get(name);
|
|
34
|
+
}
|
|
35
|
+
getOAuth(name) {
|
|
36
|
+
const strategy = this.strategies.get(name);
|
|
37
|
+
if (strategy && this.isOAuthStrategy(strategy)) {
|
|
38
|
+
return strategy;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
getAll() {
|
|
43
|
+
return Array.from(this.strategies.values());
|
|
44
|
+
}
|
|
45
|
+
getAllOAuth() {
|
|
46
|
+
return this.getAll().filter(this.isOAuthStrategy);
|
|
47
|
+
}
|
|
48
|
+
has(name) {
|
|
49
|
+
return this.strategies.has(name);
|
|
50
|
+
}
|
|
51
|
+
getDefault() {
|
|
52
|
+
if (!this.defaultStrategyName) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return this.strategies.get(this.defaultStrategyName);
|
|
56
|
+
}
|
|
57
|
+
setDefault(name) {
|
|
58
|
+
if (!this.strategies.has(name)) {
|
|
59
|
+
throw new Error(`Strategy "${name}" is not registered`);
|
|
60
|
+
}
|
|
61
|
+
this.defaultStrategyName = name;
|
|
62
|
+
}
|
|
63
|
+
clear() {
|
|
64
|
+
this.strategies.clear();
|
|
65
|
+
this.defaultStrategyName = null;
|
|
66
|
+
}
|
|
67
|
+
isOAuthStrategy(strategy) {
|
|
68
|
+
return 'getAuthorizationUrl' in strategy && 'handleCallback' in strategy;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/impl/strategies/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/impl/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Utilities
|
|
3
|
+
* Utilities for parsing and validating JWT tokens
|
|
4
|
+
*/
|
|
5
|
+
import type { User } from '../../api';
|
|
6
|
+
/**
|
|
7
|
+
* Standard JWT payload claims
|
|
8
|
+
*/
|
|
9
|
+
export interface JWTPayload {
|
|
10
|
+
/** Subject (user ID) */
|
|
11
|
+
sub?: string;
|
|
12
|
+
/** Email */
|
|
13
|
+
email?: string;
|
|
14
|
+
/** Name */
|
|
15
|
+
name?: string;
|
|
16
|
+
/** Preferred username */
|
|
17
|
+
preferred_username?: string;
|
|
18
|
+
/** Given name */
|
|
19
|
+
given_name?: string;
|
|
20
|
+
/** Family name */
|
|
21
|
+
family_name?: string;
|
|
22
|
+
/** Picture/avatar URL */
|
|
23
|
+
picture?: string;
|
|
24
|
+
/** Issued at (timestamp) */
|
|
25
|
+
iat?: number;
|
|
26
|
+
/** Expiration time (timestamp) */
|
|
27
|
+
exp?: number;
|
|
28
|
+
/** Not before (timestamp) */
|
|
29
|
+
nbf?: number;
|
|
30
|
+
/** Issuer */
|
|
31
|
+
iss?: string;
|
|
32
|
+
/** Audience */
|
|
33
|
+
aud?: string | string[];
|
|
34
|
+
/** JWT ID */
|
|
35
|
+
jti?: string;
|
|
36
|
+
/** Scopes */
|
|
37
|
+
scope?: string;
|
|
38
|
+
/** Keycloak realm access */
|
|
39
|
+
realm_access?: {
|
|
40
|
+
roles: string[];
|
|
41
|
+
};
|
|
42
|
+
/** Keycloak resource access */
|
|
43
|
+
resource_access?: Record<string, {
|
|
44
|
+
roles: string[];
|
|
45
|
+
}>;
|
|
46
|
+
/** Custom claims */
|
|
47
|
+
[key: string]: unknown;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parse a JWT token and extract the payload
|
|
51
|
+
* Note: This does NOT validate the signature
|
|
52
|
+
* @param token The JWT token string
|
|
53
|
+
* @returns The decoded payload or null if invalid
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseJWT(token: string): JWTPayload | null;
|
|
56
|
+
/**
|
|
57
|
+
* Check if a JWT token is expired
|
|
58
|
+
* @param token The JWT token or parsed payload
|
|
59
|
+
* @param bufferSeconds Extra seconds before expiry to consider expired (default: 0)
|
|
60
|
+
*/
|
|
61
|
+
export declare function isJWTExpired(token: string | JWTPayload, bufferSeconds?: number): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Get expiration time from a JWT token
|
|
64
|
+
* @param token The JWT token
|
|
65
|
+
* @returns Expiration timestamp in milliseconds or null
|
|
66
|
+
*/
|
|
67
|
+
export declare function getJWTExpiry(token: string): number | null;
|
|
68
|
+
/**
|
|
69
|
+
* Extract user information from a JWT token
|
|
70
|
+
* Handles standard OIDC claims and Keycloak-specific claims
|
|
71
|
+
* @param token The JWT token
|
|
72
|
+
* @param clientId Optional client ID for Keycloak resource roles
|
|
73
|
+
*/
|
|
74
|
+
export declare function extractUserFromJWT(token: string, clientId?: string): User | null;
|
|
75
|
+
/**
|
|
76
|
+
* Calculate time until token expires
|
|
77
|
+
* @param token The JWT token
|
|
78
|
+
* @returns Time in milliseconds until expiry, or 0 if expired
|
|
79
|
+
*/
|
|
80
|
+
export declare function getTimeUntilExpiry(token: string): number;
|
|
81
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../../src/impl/utils/jwt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe;IACf,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,aAAa;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,YAAY,CAAC,EAAE;QACb,KAAK,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;IACF,+BAA+B;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACtD,oBAAoB;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAqBzD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,aAAa,SAAI,GAAG,OAAO,CAQnF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAkChF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMxD"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Utilities
|
|
3
|
+
* Utilities for parsing and validating JWT tokens
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Parse a JWT token and extract the payload
|
|
7
|
+
* Note: This does NOT validate the signature
|
|
8
|
+
* @param token The JWT token string
|
|
9
|
+
* @returns The decoded payload or null if invalid
|
|
10
|
+
*/
|
|
11
|
+
export function parseJWT(token) {
|
|
12
|
+
try {
|
|
13
|
+
const parts = token.split('.');
|
|
14
|
+
if (parts.length !== 3) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const payload = parts[1];
|
|
18
|
+
// Handle base64url encoding
|
|
19
|
+
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
20
|
+
const jsonPayload = decodeURIComponent(atob(base64)
|
|
21
|
+
.split('')
|
|
22
|
+
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
23
|
+
.join(''));
|
|
24
|
+
return JSON.parse(jsonPayload);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if a JWT token is expired
|
|
32
|
+
* @param token The JWT token or parsed payload
|
|
33
|
+
* @param bufferSeconds Extra seconds before expiry to consider expired (default: 0)
|
|
34
|
+
*/
|
|
35
|
+
export function isJWTExpired(token, bufferSeconds = 0) {
|
|
36
|
+
const payload = typeof token === 'string' ? parseJWT(token) : token;
|
|
37
|
+
if (!payload?.exp) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
const now = Math.floor(Date.now() / 1000);
|
|
41
|
+
return payload.exp - bufferSeconds <= now;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get expiration time from a JWT token
|
|
45
|
+
* @param token The JWT token
|
|
46
|
+
* @returns Expiration timestamp in milliseconds or null
|
|
47
|
+
*/
|
|
48
|
+
export function getJWTExpiry(token) {
|
|
49
|
+
const payload = parseJWT(token);
|
|
50
|
+
if (!payload?.exp) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return payload.exp * 1000;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Extract user information from a JWT token
|
|
57
|
+
* Handles standard OIDC claims and Keycloak-specific claims
|
|
58
|
+
* @param token The JWT token
|
|
59
|
+
* @param clientId Optional client ID for Keycloak resource roles
|
|
60
|
+
*/
|
|
61
|
+
export function extractUserFromJWT(token, clientId) {
|
|
62
|
+
const payload = parseJWT(token);
|
|
63
|
+
if (!payload) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// Extract roles from Keycloak format
|
|
67
|
+
const realmRoles = payload.realm_access?.roles || [];
|
|
68
|
+
const resourceRoles = clientId
|
|
69
|
+
? payload.resource_access?.[clientId]?.roles || []
|
|
70
|
+
: [];
|
|
71
|
+
const roles = [...new Set([...realmRoles, ...resourceRoles])];
|
|
72
|
+
// Extract permissions (scopes)
|
|
73
|
+
const permissions = payload.scope?.split(' ').filter(Boolean) || [];
|
|
74
|
+
// Build user object
|
|
75
|
+
const user = {
|
|
76
|
+
id: payload.sub || '',
|
|
77
|
+
email: payload.email || '',
|
|
78
|
+
name: payload.name || payload.preferred_username || '',
|
|
79
|
+
username: payload.preferred_username,
|
|
80
|
+
avatar: payload.picture,
|
|
81
|
+
roles,
|
|
82
|
+
permissions,
|
|
83
|
+
metadata: {
|
|
84
|
+
iss: payload.iss,
|
|
85
|
+
aud: payload.aud,
|
|
86
|
+
iat: payload.iat,
|
|
87
|
+
exp: payload.exp
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
return user;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Calculate time until token expires
|
|
94
|
+
* @param token The JWT token
|
|
95
|
+
* @returns Time in milliseconds until expiry, or 0 if expired
|
|
96
|
+
*/
|
|
97
|
+
export function getTimeUntilExpiry(token) {
|
|
98
|
+
const expiry = getJWTExpiry(token);
|
|
99
|
+
if (!expiry) {
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
return Math.max(0, expiry - Date.now());
|
|
103
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (Proof Key for Code Exchange) Utilities
|
|
3
|
+
* Used for secure OAuth2 authorization code flow
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generate a cryptographically random string for state or code verifier
|
|
7
|
+
* @param length Length of the string (default: 43 for code verifier)
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateRandomString(length?: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Generate a state parameter for OAuth
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateState(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Generate a code verifier for PKCE
|
|
16
|
+
* Must be between 43 and 128 characters
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateCodeVerifier(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Generate a code challenge from a code verifier
|
|
21
|
+
* Uses SHA-256 hashing as recommended by RFC 7636
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateCodeChallenge(verifier: string): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* PKCE state stored during OAuth flow
|
|
26
|
+
*/
|
|
27
|
+
export interface PKCEState {
|
|
28
|
+
state: string;
|
|
29
|
+
codeVerifier: string;
|
|
30
|
+
createdAt: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Store PKCE state for later verification
|
|
34
|
+
*/
|
|
35
|
+
export declare function storePKCEState(pkceState: PKCEState): void;
|
|
36
|
+
/**
|
|
37
|
+
* Retrieve stored PKCE state
|
|
38
|
+
*/
|
|
39
|
+
export declare function retrievePKCEState(): PKCEState | null;
|
|
40
|
+
/**
|
|
41
|
+
* Clear stored PKCE state
|
|
42
|
+
*/
|
|
43
|
+
export declare function clearPKCEState(): void;
|
|
44
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../../src/impl/utils/pkce.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,SAAK,GAAG,MAAM,CAIxD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7E;AAiBD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAOD;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAOzD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,SAAS,GAAG,IAAI,CAWpD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAMrC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (Proof Key for Code Exchange) Utilities
|
|
3
|
+
* Used for secure OAuth2 authorization code flow
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generate a cryptographically random string for state or code verifier
|
|
7
|
+
* @param length Length of the string (default: 43 for code verifier)
|
|
8
|
+
*/
|
|
9
|
+
export function generateRandomString(length = 43) {
|
|
10
|
+
const array = new Uint8Array(length);
|
|
11
|
+
crypto.getRandomValues(array);
|
|
12
|
+
return base64UrlEncode(array);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate a state parameter for OAuth
|
|
16
|
+
*/
|
|
17
|
+
export function generateState() {
|
|
18
|
+
return generateRandomString(32);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generate a code verifier for PKCE
|
|
22
|
+
* Must be between 43 and 128 characters
|
|
23
|
+
*/
|
|
24
|
+
export function generateCodeVerifier() {
|
|
25
|
+
return generateRandomString(43);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generate a code challenge from a code verifier
|
|
29
|
+
* Uses SHA-256 hashing as recommended by RFC 7636
|
|
30
|
+
*/
|
|
31
|
+
export async function generateCodeChallenge(verifier) {
|
|
32
|
+
const encoder = new TextEncoder();
|
|
33
|
+
const data = encoder.encode(verifier);
|
|
34
|
+
const hash = await crypto.subtle.digest('SHA-256', data);
|
|
35
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Base64 URL encode a byte array
|
|
39
|
+
* Uses URL-safe alphabet without padding as per RFC 4648
|
|
40
|
+
*/
|
|
41
|
+
function base64UrlEncode(buffer) {
|
|
42
|
+
let binary = '';
|
|
43
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
44
|
+
binary += String.fromCharCode(buffer[i]);
|
|
45
|
+
}
|
|
46
|
+
return btoa(binary)
|
|
47
|
+
.replace(/\+/g, '-')
|
|
48
|
+
.replace(/\//g, '_')
|
|
49
|
+
.replace(/=/g, '');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Storage key for PKCE state
|
|
53
|
+
*/
|
|
54
|
+
const PKCE_STORAGE_KEY = '@hamak/auth:pkce';
|
|
55
|
+
/**
|
|
56
|
+
* Store PKCE state for later verification
|
|
57
|
+
*/
|
|
58
|
+
export function storePKCEState(pkceState) {
|
|
59
|
+
try {
|
|
60
|
+
sessionStorage.setItem(PKCE_STORAGE_KEY, JSON.stringify(pkceState));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Fallback to memory if sessionStorage is not available
|
|
64
|
+
window[PKCE_STORAGE_KEY] = pkceState;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Retrieve stored PKCE state
|
|
69
|
+
*/
|
|
70
|
+
export function retrievePKCEState() {
|
|
71
|
+
try {
|
|
72
|
+
const stored = sessionStorage.getItem(PKCE_STORAGE_KEY);
|
|
73
|
+
if (stored) {
|
|
74
|
+
return JSON.parse(stored);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Fallback to memory
|
|
79
|
+
return window[PKCE_STORAGE_KEY] || null;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Clear stored PKCE state
|
|
85
|
+
*/
|
|
86
|
+
export function clearPKCEState() {
|
|
87
|
+
try {
|
|
88
|
+
sessionStorage.removeItem(PKCE_STORAGE_KEY);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
delete window[PKCE_STORAGE_KEY];
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hamak/auth
|
|
3
|
+
*
|
|
4
|
+
* Complete authentication plugin with password, OAuth2, and Keycloak support.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { createAuthPlugin, AUTH_SERVICE_TOKEN } from '@hamak/auth';
|
|
8
|
+
* import type { IAuthService } from '@hamak/auth/api';
|
|
9
|
+
* import type { ITokenStorage } from '@hamak/auth/spi';
|
|
10
|
+
*/
|
|
11
|
+
export * from './impl';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,QAAQ,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hamak/auth
|
|
3
|
+
*
|
|
4
|
+
* Complete authentication plugin with password, OAuth2, and Keycloak support.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { createAuthPlugin, AUTH_SERVICE_TOKEN } from '@hamak/auth';
|
|
8
|
+
* import type { IAuthService } from '@hamak/auth/api';
|
|
9
|
+
* import type { ITokenStorage } from '@hamak/auth/spi';
|
|
10
|
+
*/
|
|
11
|
+
export * from './impl';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Guard Interface
|
|
3
|
+
* Navigation guards for protected routes
|
|
4
|
+
*/
|
|
5
|
+
import type { IAuthService, User } from '../../api';
|
|
6
|
+
/**
|
|
7
|
+
* Route metadata that can be used for auth decisions
|
|
8
|
+
*/
|
|
9
|
+
export interface RouteMeta {
|
|
10
|
+
/** Whether the route requires authentication */
|
|
11
|
+
requiresAuth?: boolean;
|
|
12
|
+
/** Required roles to access the route */
|
|
13
|
+
roles?: string[];
|
|
14
|
+
/** Required permissions to access the route */
|
|
15
|
+
permissions?: string[];
|
|
16
|
+
/** Custom guard function */
|
|
17
|
+
guard?: (user: User | null) => boolean;
|
|
18
|
+
/** Path to redirect to if auth fails */
|
|
19
|
+
redirectTo?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Guard context passed to auth guards
|
|
23
|
+
*/
|
|
24
|
+
export interface AuthGuardContext {
|
|
25
|
+
/** The path being navigated to */
|
|
26
|
+
toPath: string;
|
|
27
|
+
/** The path being navigated from */
|
|
28
|
+
fromPath: string | null;
|
|
29
|
+
/** Route metadata */
|
|
30
|
+
meta?: RouteMeta;
|
|
31
|
+
/** The auth service instance */
|
|
32
|
+
authService: IAuthService;
|
|
33
|
+
/** Abort navigation */
|
|
34
|
+
abort: () => void;
|
|
35
|
+
/** Redirect to a different path */
|
|
36
|
+
redirect: (path: string) => void;
|
|
37
|
+
/** Continue with navigation */
|
|
38
|
+
proceed: () => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Auth guard function type
|
|
42
|
+
*/
|
|
43
|
+
export type AuthGuardFn = (context: AuthGuardContext) => void | Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Auth guard configuration
|
|
46
|
+
*/
|
|
47
|
+
export interface AuthGuardConfig {
|
|
48
|
+
/** Path to redirect unauthenticated users */
|
|
49
|
+
loginPath: string;
|
|
50
|
+
/** Path to redirect unauthorized users (has auth but wrong permissions) */
|
|
51
|
+
unauthorizedPath?: string;
|
|
52
|
+
/** Path to redirect after successful login */
|
|
53
|
+
afterLoginPath?: string;
|
|
54
|
+
/** Whether to store the intended path for redirect after login */
|
|
55
|
+
preserveIntendedPath?: boolean;
|
|
56
|
+
/** Key to use when storing intended path */
|
|
57
|
+
intendedPathKey?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Auth guard manager interface
|
|
61
|
+
* Manages navigation guards for authentication
|
|
62
|
+
*/
|
|
63
|
+
export interface IAuthGuardManager {
|
|
64
|
+
/**
|
|
65
|
+
* Register a global auth guard
|
|
66
|
+
* @param guard The guard function
|
|
67
|
+
* @returns Unregister function
|
|
68
|
+
*/
|
|
69
|
+
register(guard: AuthGuardFn): () => void;
|
|
70
|
+
/**
|
|
71
|
+
* Check if navigation to a path is allowed
|
|
72
|
+
* @param context The guard context
|
|
73
|
+
* @returns Whether navigation should proceed
|
|
74
|
+
*/
|
|
75
|
+
check(context: AuthGuardContext): Promise<boolean>;
|
|
76
|
+
/**
|
|
77
|
+
* Create a guard that requires authentication
|
|
78
|
+
* @param config Guard configuration
|
|
79
|
+
*/
|
|
80
|
+
requireAuth(config?: Partial<AuthGuardConfig>): AuthGuardFn;
|
|
81
|
+
/**
|
|
82
|
+
* Create a guard that requires specific roles
|
|
83
|
+
* @param roles Required roles (user must have at least one)
|
|
84
|
+
* @param config Guard configuration
|
|
85
|
+
*/
|
|
86
|
+
requireRoles(roles: string[], config?: Partial<AuthGuardConfig>): AuthGuardFn;
|
|
87
|
+
/**
|
|
88
|
+
* Create a guard that requires specific permissions
|
|
89
|
+
* @param permissions Required permissions (user must have all)
|
|
90
|
+
* @param config Guard configuration
|
|
91
|
+
*/
|
|
92
|
+
requirePermissions(permissions: string[], config?: Partial<AuthGuardConfig>): AuthGuardFn;
|
|
93
|
+
/**
|
|
94
|
+
* Store the intended path for redirect after login
|
|
95
|
+
* @param path The path the user intended to visit
|
|
96
|
+
*/
|
|
97
|
+
setIntendedPath(path: string): void;
|
|
98
|
+
/**
|
|
99
|
+
* Get and clear the intended path
|
|
100
|
+
* @returns The intended path or null
|
|
101
|
+
*/
|
|
102
|
+
getIntendedPath(): string | null;
|
|
103
|
+
/**
|
|
104
|
+
* Clear all registered guards
|
|
105
|
+
*/
|
|
106
|
+
clear(): void;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=AuthGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthGuard.d.ts","sourceRoot":"","sources":["../../../src/spi/guards/AuthGuard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,gDAAgD;IAChD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,OAAO,CAAC;IACvC,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,qBAAqB;IACrB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,gCAAgC;IAChC,WAAW,EAAE,YAAY,CAAC;IAC1B,uBAAuB;IACvB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,mCAAmC;IACnC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,+BAA+B;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,IAAI,CAAC;IAEzC;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnD;;;OAGG;IACH,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,WAAW,CAAC;IAE5D;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,WAAW,CAAC;IAE9E;;;;OAIG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,WAAW,CAAC;IAE1F;;;OAGG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpC;;;OAGG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI,CAAC;IAEjC;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;CACf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/spi/guards/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './AuthGuard';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hamak/auth/spi
|
|
3
|
+
*
|
|
4
|
+
* Service Provider Interfaces for the authentication system.
|
|
5
|
+
* Contains extension points for implementing custom strategies,
|
|
6
|
+
* storage providers, and guards.
|
|
7
|
+
*/
|
|
8
|
+
export * from '../api';
|
|
9
|
+
export * from './strategies';
|
|
10
|
+
export * from './storage';
|
|
11
|
+
export * from './guards';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/spi/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,cAAc,QAAQ,CAAC;AAGvB,cAAc,cAAc,CAAC;AAG7B,cAAc,WAAW,CAAC;AAG1B,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hamak/auth/spi
|
|
3
|
+
*
|
|
4
|
+
* Service Provider Interfaces for the authentication system.
|
|
5
|
+
* Contains extension points for implementing custom strategies,
|
|
6
|
+
* storage providers, and guards.
|
|
7
|
+
*/
|
|
8
|
+
// Re-export API types for convenience
|
|
9
|
+
export * from '../api';
|
|
10
|
+
// Strategy interfaces
|
|
11
|
+
export * from './strategies';
|
|
12
|
+
// Storage interfaces
|
|
13
|
+
export * from './storage';
|
|
14
|
+
// Guard interfaces
|
|
15
|
+
export * from './guards';
|