@intelicity/gates-sdk 0.1.5 → 0.2.0
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 +110 -292
- package/dist/auth/middleware.d.ts +10 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +31 -40
- package/dist/errors/error.d.ts +21 -13
- package/dist/errors/error.d.ts.map +1 -1
- package/dist/errors/error.js +51 -15
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/models/user.d.ts +6 -7
- package/dist/models/user.d.ts.map +1 -1
- package/dist/services/admin-service.d.ts +26 -0
- package/dist/services/admin-service.d.ts.map +1 -0
- package/dist/services/admin-service.js +92 -0
- package/dist/services/auth-service.d.ts +4 -7
- package/dist/services/auth-service.d.ts.map +1 -1
- package/dist/services/auth-service.js +69 -54
- package/dist/services/client-auth.d.ts +22 -0
- package/dist/services/client-auth.d.ts.map +1 -0
- package/dist/services/client-auth.js +75 -0
- package/dist/services/user-service.d.ts +2 -2
- package/dist/services/user-service.d.ts.map +1 -1
- package/dist/services/user-service.js +43 -23
- package/package.json +5 -2
package/dist/errors/error.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Base error class for authentication-related errors
|
|
4
|
-
*/
|
|
5
|
-
export class AuthenticationError extends Error {
|
|
1
|
+
export class GatesError extends Error {
|
|
6
2
|
code;
|
|
7
3
|
constructor(message, code) {
|
|
8
4
|
super(message);
|
|
9
5
|
this.code = code;
|
|
6
|
+
this.name = "GatesError";
|
|
7
|
+
Object.setPrototypeOf(this, GatesError.prototype);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class AuthenticationError extends GatesError {
|
|
11
|
+
constructor(message, code) {
|
|
12
|
+
super(message, code);
|
|
10
13
|
this.name = "AuthenticationError";
|
|
11
14
|
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Error thrown when a token has expired
|
|
16
|
-
*/
|
|
17
17
|
export class TokenExpiredError extends AuthenticationError {
|
|
18
18
|
constructor(message = "Token has expired") {
|
|
19
19
|
super(message, "TOKEN_EXPIRED");
|
|
@@ -21,9 +21,6 @@ export class TokenExpiredError extends AuthenticationError {
|
|
|
21
21
|
Object.setPrototypeOf(this, TokenExpiredError.prototype);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Error thrown when a token is invalid
|
|
26
|
-
*/
|
|
27
24
|
export class InvalidTokenError extends AuthenticationError {
|
|
28
25
|
constructor(message = "Invalid token") {
|
|
29
26
|
super(message, "INVALID_TOKEN");
|
|
@@ -31,13 +28,52 @@ export class InvalidTokenError extends AuthenticationError {
|
|
|
31
28
|
Object.setPrototypeOf(this, InvalidTokenError.prototype);
|
|
32
29
|
}
|
|
33
30
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Error thrown when authorization header is missing
|
|
36
|
-
*/
|
|
37
31
|
export class MissingAuthorizationError extends AuthenticationError {
|
|
38
|
-
constructor(message = "
|
|
32
|
+
constructor(message = "Authorization header is missing") {
|
|
39
33
|
super(message, "MISSING_AUTHORIZATION");
|
|
40
34
|
this.name = "MissingAuthorizationError";
|
|
41
35
|
Object.setPrototypeOf(this, MissingAuthorizationError.prototype);
|
|
42
36
|
}
|
|
43
37
|
}
|
|
38
|
+
export class UnauthorizedGroupError extends AuthenticationError {
|
|
39
|
+
requiredGroups;
|
|
40
|
+
constructor(message, requiredGroups) {
|
|
41
|
+
super(message, "UNAUTHORIZED_GROUP");
|
|
42
|
+
this.requiredGroups = requiredGroups;
|
|
43
|
+
this.name = "UnauthorizedGroupError";
|
|
44
|
+
Object.setPrototypeOf(this, UnauthorizedGroupError.prototype);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export class ApiError extends GatesError {
|
|
48
|
+
statusCode;
|
|
49
|
+
constructor(message, statusCode, code) {
|
|
50
|
+
super(message, code);
|
|
51
|
+
this.statusCode = statusCode;
|
|
52
|
+
this.name = "ApiError";
|
|
53
|
+
Object.setPrototypeOf(this, ApiError.prototype);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export class ApiRequestError extends ApiError {
|
|
57
|
+
constructor(message, statusCode) {
|
|
58
|
+
super(message, statusCode, "API_REQUEST_ERROR");
|
|
59
|
+
this.name = "ApiRequestError";
|
|
60
|
+
Object.setPrototypeOf(this, ApiRequestError.prototype);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class MissingParameterError extends GatesError {
|
|
64
|
+
constructor(parameterName) {
|
|
65
|
+
super(`Missing required parameter: ${parameterName}`, "MISSING_PARAMETER");
|
|
66
|
+
this.name = "MissingParameterError";
|
|
67
|
+
Object.setPrototypeOf(this, MissingParameterError.prototype);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export class InvalidParameterError extends GatesError {
|
|
71
|
+
constructor(parameterName, reason) {
|
|
72
|
+
const message = reason
|
|
73
|
+
? `Invalid parameter '${parameterName}': ${reason}`
|
|
74
|
+
: `Invalid parameter: ${parameterName}`;
|
|
75
|
+
super(message, "INVALID_PARAMETER");
|
|
76
|
+
this.name = "InvalidParameterError";
|
|
77
|
+
Object.setPrototypeOf(this, InvalidParameterError.prototype);
|
|
78
|
+
}
|
|
79
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export type { GatesUser
|
|
2
|
-
export
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export { AuthenticationError, TokenExpiredError, InvalidTokenError, MissingAuthorizationError, } from "./errors/error.js";
|
|
1
|
+
export type { GatesUser, GatesRole } from "./models/user.js";
|
|
2
|
+
export { AuthService, type VerifyOptions } from "./services/auth-service.js";
|
|
3
|
+
export { GatesAdminService, type GatesAdminConfig, type CreateUserParams, type CreateUserResponse, type UpdateUserParams, } from "./services/admin-service.js";
|
|
4
|
+
export { extractToken, authenticate, authorize, handleAuth, type AuthHandlerConfig, } from "./auth/middleware.js";
|
|
5
|
+
export { GatesError, AuthenticationError, TokenExpiredError, InvalidTokenError, MissingAuthorizationError, UnauthorizedGroupError, ApiError, ApiRequestError, MissingParameterError, InvalidParameterError, } from "./errors/error.js";
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE7E,OAAO,EACL,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,UAAU,EACV,KAAK,iBAAiB,GACvB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,EACtB,QAAQ,EACR,eAAe,EACf,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
1
|
+
// Services
|
|
2
|
+
export { AuthService } from "./services/auth-service.js";
|
|
3
|
+
export { GatesAdminService, } from "./services/admin-service.js";
|
|
4
|
+
// Middleware
|
|
5
|
+
export { extractToken, authenticate, authorize, handleAuth, } from "./auth/middleware.js";
|
|
6
|
+
// Errors
|
|
7
|
+
export { GatesError, AuthenticationError, TokenExpiredError, InvalidTokenError, MissingAuthorizationError, UnauthorizedGroupError, ApiError, ApiRequestError, MissingParameterError, InvalidParameterError, } from "./errors/error.js";
|
package/dist/models/user.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
* Tipagem base do payload que sai do Cognito.
|
|
3
|
-
* Você pode estender com claims próprias (ex.: permissions, system_access).
|
|
4
|
-
*/
|
|
1
|
+
export type GatesRole = "INTERNAL_ADMIN" | "INTERNAL_USER" | "CLIENT_ADMIN" | "CLIENT_USER";
|
|
5
2
|
export type GatesUser = {
|
|
6
3
|
user_id: string;
|
|
7
|
-
email
|
|
8
|
-
name
|
|
9
|
-
role
|
|
4
|
+
email?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
groups: string[];
|
|
8
|
+
token_use: "access" | "id";
|
|
10
9
|
exp: number;
|
|
11
10
|
iat: number;
|
|
12
11
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/models/user.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/models/user.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GACjB,gBAAgB,GAChB,eAAe,GACf,cAAc,GACd,aAAa,CAAC;AAElB,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GatesRole } from "../models/user.js";
|
|
2
|
+
export type GatesAdminConfig = {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
};
|
|
5
|
+
export type CreateUserParams = {
|
|
6
|
+
email: string;
|
|
7
|
+
name: string;
|
|
8
|
+
role: GatesRole;
|
|
9
|
+
client: string;
|
|
10
|
+
};
|
|
11
|
+
export type CreateUserResponse = {
|
|
12
|
+
sub: string;
|
|
13
|
+
};
|
|
14
|
+
export type UpdateUserParams = {
|
|
15
|
+
user_id: string;
|
|
16
|
+
clients_to_add?: string[];
|
|
17
|
+
clients_to_remove?: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare class GatesAdminService {
|
|
20
|
+
private readonly baseUrl;
|
|
21
|
+
constructor(config: GatesAdminConfig);
|
|
22
|
+
createUser(idToken: string, params: CreateUserParams): Promise<CreateUserResponse>;
|
|
23
|
+
updateUser(idToken: string, params: UpdateUserParams): Promise<void>;
|
|
24
|
+
private request;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=admin-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-service.d.ts","sourceRoot":"","sources":["../../src/services/admin-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAO9C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AASF,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,MAAM,EAAE,gBAAgB;IAO9B,UAAU,CACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,kBAAkB,CAAC;IAgDxB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;YAgB5D,OAAO;CAmCtB"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { MissingParameterError, InvalidParameterError, ApiRequestError, } from "../errors/error.js";
|
|
2
|
+
const VALID_ROLES = [
|
|
3
|
+
"INTERNAL_ADMIN",
|
|
4
|
+
"INTERNAL_USER",
|
|
5
|
+
"CLIENT_ADMIN",
|
|
6
|
+
"CLIENT_USER",
|
|
7
|
+
];
|
|
8
|
+
export class GatesAdminService {
|
|
9
|
+
baseUrl;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
if (!config.baseUrl || config.baseUrl.trim().length === 0) {
|
|
12
|
+
throw new MissingParameterError("baseUrl");
|
|
13
|
+
}
|
|
14
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
15
|
+
}
|
|
16
|
+
async createUser(idToken, params) {
|
|
17
|
+
if (!idToken) {
|
|
18
|
+
throw new MissingParameterError("idToken");
|
|
19
|
+
}
|
|
20
|
+
if (!params.email || params.email.trim().length === 0) {
|
|
21
|
+
throw new MissingParameterError("email");
|
|
22
|
+
}
|
|
23
|
+
if (!params.name || params.name.trim().length === 0) {
|
|
24
|
+
throw new MissingParameterError("name");
|
|
25
|
+
}
|
|
26
|
+
if (!params.role) {
|
|
27
|
+
throw new MissingParameterError("role");
|
|
28
|
+
}
|
|
29
|
+
if (!VALID_ROLES.includes(params.role)) {
|
|
30
|
+
throw new InvalidParameterError("role", `must be one of: ${VALID_ROLES.join(", ")}`);
|
|
31
|
+
}
|
|
32
|
+
if (!params.client || params.client.trim().length === 0) {
|
|
33
|
+
throw new MissingParameterError("client");
|
|
34
|
+
}
|
|
35
|
+
const response = await this.request("POST", "/create-user", idToken, {
|
|
36
|
+
email: params.email,
|
|
37
|
+
name: params.name,
|
|
38
|
+
role: params.role,
|
|
39
|
+
});
|
|
40
|
+
const data = (await response.json());
|
|
41
|
+
if (!data.sub) {
|
|
42
|
+
throw new ApiRequestError("Response missing 'sub' field");
|
|
43
|
+
}
|
|
44
|
+
await this.updateUser(idToken, {
|
|
45
|
+
user_id: data.sub,
|
|
46
|
+
clients_to_add: [params.client],
|
|
47
|
+
});
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
async updateUser(idToken, params) {
|
|
51
|
+
if (!idToken) {
|
|
52
|
+
throw new MissingParameterError("idToken");
|
|
53
|
+
}
|
|
54
|
+
if (!params.user_id || params.user_id.trim().length === 0) {
|
|
55
|
+
throw new MissingParameterError("user_id");
|
|
56
|
+
}
|
|
57
|
+
await this.request("PUT", "/update-user", idToken, {
|
|
58
|
+
user_id: params.user_id,
|
|
59
|
+
clients_to_add: params.clients_to_add,
|
|
60
|
+
clients_to_remove: params.clients_to_remove,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async request(method, path, idToken, body) {
|
|
64
|
+
let response;
|
|
65
|
+
try {
|
|
66
|
+
response = await fetch(`${this.baseUrl}${path}`, {
|
|
67
|
+
method,
|
|
68
|
+
headers: {
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
Authorization: `Bearer ${idToken}`,
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify(body),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : "Unknown network error";
|
|
77
|
+
throw new ApiRequestError(`Failed to reach Gates API: ${message}`);
|
|
78
|
+
}
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
let detail;
|
|
81
|
+
try {
|
|
82
|
+
const errorBody = (await response.json());
|
|
83
|
+
detail = errorBody.message ?? response.statusText;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
detail = response.statusText;
|
|
87
|
+
}
|
|
88
|
+
throw new ApiRequestError(`Gates API error: ${detail}`, response.status);
|
|
89
|
+
}
|
|
90
|
+
return response;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -2,17 +2,14 @@ import { GatesUser } from "../models/user.js";
|
|
|
2
2
|
export type VerifyOptions = {
|
|
3
3
|
region: string;
|
|
4
4
|
userPoolId: string;
|
|
5
|
-
|
|
6
|
-
requiredGroup?: string | string[];
|
|
5
|
+
clientId: string;
|
|
7
6
|
};
|
|
8
|
-
export declare class
|
|
7
|
+
export declare class AuthService {
|
|
9
8
|
private readonly region;
|
|
10
9
|
private readonly userPoolId;
|
|
11
|
-
private readonly
|
|
12
|
-
|
|
13
|
-
constructor(region: string, userPoolId: string, audience: string, requiredGroup?: string | string[]);
|
|
10
|
+
private readonly clientId;
|
|
11
|
+
constructor(region: string, userPoolId: string, clientId: string);
|
|
14
12
|
private get issuer();
|
|
15
|
-
isMemberOf(groups?: string[]): boolean;
|
|
16
13
|
verifyToken(token: string): Promise<GatesUser>;
|
|
17
14
|
}
|
|
18
15
|
//# sourceMappingURL=auth-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-service.d.ts","sourceRoot":"","sources":["../../src/services/auth-service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-service.d.ts","sourceRoot":"","sources":["../../src/services/auth-service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAQ9C,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAiChE,OAAO,KAAK,MAAM,GAEjB;IAEK,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAqFrD"}
|
|
@@ -1,80 +1,95 @@
|
|
|
1
|
-
import { jwtVerify } from "jose";
|
|
1
|
+
import { jwtVerify, errors as joseErrors } from "jose";
|
|
2
2
|
import { getJwks } from "../cache/jwks-cache.js";
|
|
3
|
-
|
|
3
|
+
import { InvalidParameterError, MissingParameterError, TokenExpiredError, InvalidTokenError, } from "../errors/error.js";
|
|
4
|
+
export class AuthService {
|
|
4
5
|
region;
|
|
5
6
|
userPoolId;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
constructor(region, userPoolId, audience, requiredGroup) {
|
|
7
|
+
clientId;
|
|
8
|
+
constructor(region, userPoolId, clientId) {
|
|
9
9
|
if (!region || typeof region !== "string" || region.trim().length === 0) {
|
|
10
|
-
throw new
|
|
10
|
+
throw new MissingParameterError("region");
|
|
11
11
|
}
|
|
12
12
|
if (!userPoolId ||
|
|
13
13
|
typeof userPoolId !== "string" ||
|
|
14
14
|
userPoolId.trim().length === 0) {
|
|
15
|
-
throw new
|
|
15
|
+
throw new MissingParameterError("userPoolId");
|
|
16
16
|
}
|
|
17
|
-
if (!
|
|
18
|
-
typeof
|
|
19
|
-
|
|
20
|
-
throw new
|
|
17
|
+
if (!clientId ||
|
|
18
|
+
typeof clientId !== "string" ||
|
|
19
|
+
clientId.trim().length === 0) {
|
|
20
|
+
throw new MissingParameterError("clientId");
|
|
21
21
|
}
|
|
22
|
-
// Validar formato do userPoolId (deve seguir padrão AWS)
|
|
23
22
|
if (!/^[a-zA-Z0-9_-]+$/.test(userPoolId)) {
|
|
24
|
-
throw new
|
|
23
|
+
throw new InvalidParameterError("userPoolId", "must follow AWS format (alphanumeric, hyphens, and underscores only)");
|
|
25
24
|
}
|
|
26
25
|
this.region = region;
|
|
27
26
|
this.userPoolId = userPoolId;
|
|
28
|
-
this.
|
|
29
|
-
this.requiredGroup = requiredGroup;
|
|
27
|
+
this.clientId = clientId;
|
|
30
28
|
}
|
|
31
29
|
get issuer() {
|
|
32
30
|
return `https://cognito-idp.${this.region}.amazonaws.com/${this.userPoolId}`;
|
|
33
31
|
}
|
|
34
|
-
isMemberOf(groups = []) {
|
|
35
|
-
if (!Array.isArray(groups)) {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
if (!this.requiredGroup) {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
const requiredGroups = Array.isArray(this.requiredGroup)
|
|
42
|
-
? this.requiredGroup
|
|
43
|
-
: [this.requiredGroup];
|
|
44
|
-
return groups.some((g) => requiredGroups.includes(g));
|
|
45
|
-
}
|
|
46
32
|
async verifyToken(token) {
|
|
47
33
|
if (!token) {
|
|
48
|
-
throw new
|
|
34
|
+
throw new MissingParameterError("token");
|
|
49
35
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
36
|
+
try {
|
|
37
|
+
const jwks = getJwks(this.region, this.userPoolId);
|
|
38
|
+
const { payload } = await jwtVerify(token, jwks, {
|
|
39
|
+
issuer: this.issuer,
|
|
40
|
+
});
|
|
41
|
+
const tokenUse = payload.token_use;
|
|
42
|
+
if (tokenUse !== "access" && tokenUse !== "id") {
|
|
43
|
+
throw new InvalidTokenError(`Unsupported token_use: expected "access" or "id", got "${tokenUse}"`);
|
|
44
|
+
}
|
|
45
|
+
if (tokenUse === "access") {
|
|
46
|
+
const clientId = payload.client_id;
|
|
47
|
+
if (clientId !== this.clientId) {
|
|
48
|
+
throw new InvalidTokenError("Token client_id does not match the expected clientId");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const aud = payload.aud;
|
|
53
|
+
const audValue = Array.isArray(aud) ? aud[0] : aud;
|
|
54
|
+
if (audValue !== this.clientId) {
|
|
55
|
+
throw new InvalidTokenError("Token audience does not match the expected clientId");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const groups = payload["cognito:groups"] ?? [];
|
|
59
|
+
const user = {
|
|
60
|
+
user_id: payload.sub,
|
|
61
|
+
email: payload.email,
|
|
62
|
+
name: payload.name,
|
|
63
|
+
role: payload["custom:general_role"],
|
|
64
|
+
groups,
|
|
65
|
+
token_use: tokenUse,
|
|
66
|
+
exp: payload.exp,
|
|
67
|
+
iat: payload.iat,
|
|
68
|
+
};
|
|
69
|
+
return user;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof InvalidTokenError ||
|
|
73
|
+
error instanceof MissingParameterError) {
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
if (error instanceof joseErrors.JWTExpired) {
|
|
77
|
+
throw new TokenExpiredError();
|
|
78
|
+
}
|
|
79
|
+
if (error instanceof joseErrors.JWTInvalid) {
|
|
80
|
+
throw new InvalidTokenError("Invalid or malformed token");
|
|
59
81
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
if (error instanceof Error) {
|
|
83
|
+
if (error.message.includes("expired")) {
|
|
84
|
+
throw new TokenExpiredError(error.message);
|
|
85
|
+
}
|
|
86
|
+
if (error.message.includes("signature") ||
|
|
87
|
+
error.message.includes("invalid")) {
|
|
88
|
+
throw new InvalidTokenError(error.message);
|
|
89
|
+
}
|
|
90
|
+
throw new InvalidTokenError(`Token verification failed: ${error.message}`);
|
|
67
91
|
}
|
|
92
|
+
throw new InvalidTokenError("Token verification failed");
|
|
68
93
|
}
|
|
69
|
-
// Mapear o payload do Cognito para o formato do GatesUser
|
|
70
|
-
const user = {
|
|
71
|
-
user_id: payload.sub,
|
|
72
|
-
email: payload.email,
|
|
73
|
-
name: payload.name,
|
|
74
|
-
role: payload["custom:general_role"],
|
|
75
|
-
exp: payload.exp,
|
|
76
|
-
iat: payload.iat,
|
|
77
|
-
};
|
|
78
|
-
return user;
|
|
79
94
|
}
|
|
80
95
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type ClientCredentialsConfig = {
|
|
2
|
+
domain: string;
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret: string;
|
|
5
|
+
scopes?: string[];
|
|
6
|
+
};
|
|
7
|
+
export type ClientToken = {
|
|
8
|
+
access_token: string;
|
|
9
|
+
expires_in: number;
|
|
10
|
+
token_type: string;
|
|
11
|
+
};
|
|
12
|
+
export declare class GatesClientAuth {
|
|
13
|
+
private readonly tokenEndpoint;
|
|
14
|
+
private readonly clientId;
|
|
15
|
+
private readonly clientSecret;
|
|
16
|
+
private readonly scopes;
|
|
17
|
+
private cachedToken;
|
|
18
|
+
constructor(config: ClientCredentialsConfig);
|
|
19
|
+
getAccessToken(): Promise<string>;
|
|
20
|
+
clearCache(): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=client-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-auth.d.ts","sourceRoot":"","sources":["../../src/services/client-auth.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,WAAW,CAAqD;gBAE5D,MAAM,EAAE,uBAAuB;IAkBrC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAiEvC,UAAU,IAAI,IAAI;CAGnB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { MissingParameterError, ClientCredentialsError, } from "../errors/error.js";
|
|
2
|
+
export class GatesClientAuth {
|
|
3
|
+
tokenEndpoint;
|
|
4
|
+
clientId;
|
|
5
|
+
clientSecret;
|
|
6
|
+
scopes;
|
|
7
|
+
cachedToken = null;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
if (!config.domain || config.domain.trim().length === 0) {
|
|
10
|
+
throw new MissingParameterError("domain");
|
|
11
|
+
}
|
|
12
|
+
if (!config.clientId || config.clientId.trim().length === 0) {
|
|
13
|
+
throw new MissingParameterError("clientId");
|
|
14
|
+
}
|
|
15
|
+
if (!config.clientSecret || config.clientSecret.trim().length === 0) {
|
|
16
|
+
throw new MissingParameterError("clientSecret");
|
|
17
|
+
}
|
|
18
|
+
const domain = config.domain.replace(/\/$/, "");
|
|
19
|
+
this.tokenEndpoint = `https://${domain}/oauth2/token`;
|
|
20
|
+
this.clientId = config.clientId;
|
|
21
|
+
this.clientSecret = config.clientSecret;
|
|
22
|
+
this.scopes = config.scopes ?? [];
|
|
23
|
+
}
|
|
24
|
+
async getAccessToken() {
|
|
25
|
+
if (this.cachedToken && Date.now() < this.cachedToken.expiresAt) {
|
|
26
|
+
return this.cachedToken.token;
|
|
27
|
+
}
|
|
28
|
+
const body = new URLSearchParams({
|
|
29
|
+
grant_type: "client_credentials",
|
|
30
|
+
});
|
|
31
|
+
if (this.scopes.length > 0) {
|
|
32
|
+
body.set("scope", this.scopes.join(" "));
|
|
33
|
+
}
|
|
34
|
+
const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64");
|
|
35
|
+
let response;
|
|
36
|
+
try {
|
|
37
|
+
response = await fetch(this.tokenEndpoint, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
41
|
+
Authorization: `Basic ${credentials}`,
|
|
42
|
+
},
|
|
43
|
+
body: body.toString(),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : "Unknown network error";
|
|
48
|
+
throw new ClientCredentialsError(`Failed to reach token endpoint: ${message}`);
|
|
49
|
+
}
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
let detail;
|
|
52
|
+
try {
|
|
53
|
+
const errorBody = (await response.json());
|
|
54
|
+
detail = errorBody.error ?? response.statusText;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
detail = response.statusText;
|
|
58
|
+
}
|
|
59
|
+
throw new ClientCredentialsError(`Token request failed (${response.status}): ${detail}`);
|
|
60
|
+
}
|
|
61
|
+
const data = (await response.json());
|
|
62
|
+
if (!data.access_token) {
|
|
63
|
+
throw new ClientCredentialsError("Token response missing access_token field");
|
|
64
|
+
}
|
|
65
|
+
const bufferMs = 30_000;
|
|
66
|
+
this.cachedToken = {
|
|
67
|
+
token: data.access_token,
|
|
68
|
+
expiresAt: Date.now() + data.expires_in * 1000 - bufferMs,
|
|
69
|
+
};
|
|
70
|
+
return data.access_token;
|
|
71
|
+
}
|
|
72
|
+
clearCache() {
|
|
73
|
+
this.cachedToken = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -27,9 +27,9 @@ export declare class GatesUserService {
|
|
|
27
27
|
getAllUsers(idToken: string): Promise<UserListResponse>;
|
|
28
28
|
/**
|
|
29
29
|
* Busca um usuário específico por ID
|
|
30
|
-
* @param
|
|
30
|
+
* @param idToken Token de autenticação
|
|
31
31
|
* @returns Dados do usuário
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
getProfile(idToken: string): Promise<UserProfile>;
|
|
34
34
|
}
|
|
35
35
|
//# sourceMappingURL=user-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-service.d.ts","sourceRoot":"","sources":["../../src/services/user-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAoB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"user-service.d.ts","sourceRoot":"","sources":["../../src/services/user-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAoB,MAAM,sBAAsB,CAAC;AAOrE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAeD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;gBAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAQ3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAExB;IAEF;;;;;OAKG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,GAEd,OAAO,CAAC,gBAAgB,CAAC;IAqE5B;;;;OAIG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;CAuExD"}
|