@ainetwork/adk-provider-auth-m365 0.3.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/dist/chunk-D55IH5LY.js +119 -0
- package/dist/chunk-D55IH5LY.js.map +1 -0
- package/dist/implements/m365.auth.cjs +153 -0
- package/dist/implements/m365.auth.cjs.map +1 -0
- package/dist/implements/m365.auth.d.cts +23 -0
- package/dist/implements/m365.auth.d.ts +23 -0
- package/dist/implements/m365.auth.js +7 -0
- package/dist/implements/m365.auth.js.map +1 -0
- package/dist/index.cjs +155 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/implements/m365.auth.ts +160 -0
- package/index.ts +1 -0
- package/package.json +38 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +9 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// implements/m365.auth.ts
|
|
2
|
+
import { BaseAuth } from "@ainetwork/adk/modules";
|
|
3
|
+
import jwt from "jsonwebtoken";
|
|
4
|
+
import jwksClient from "jwks-rsa";
|
|
5
|
+
var M365Auth = class extends BaseAuth {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
super();
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.cloudInstance = this.config.cloudInstance || "https://login.microsoftonline.com";
|
|
10
|
+
this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;
|
|
11
|
+
this.jwksClient = jwksClient({
|
|
12
|
+
jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,
|
|
13
|
+
cache: true,
|
|
14
|
+
cacheMaxAge: 864e5,
|
|
15
|
+
// 24 hours
|
|
16
|
+
rateLimit: true,
|
|
17
|
+
jwksRequestsPerMinute: 10
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
jwksClient;
|
|
21
|
+
cloudInstance;
|
|
22
|
+
expectedIssuer;
|
|
23
|
+
getSigningKey = (header, callback) => {
|
|
24
|
+
this.jwksClient.getSigningKey(header.kid, (err, key) => {
|
|
25
|
+
if (err) {
|
|
26
|
+
callback(err);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const signingKey = key?.getPublicKey();
|
|
30
|
+
callback(null, signingKey);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
verifyAzureADToken(token) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
jwt.verify(
|
|
36
|
+
token,
|
|
37
|
+
this.getSigningKey,
|
|
38
|
+
{
|
|
39
|
+
algorithms: ["RS256"],
|
|
40
|
+
audience: this.config.clientId,
|
|
41
|
+
issuer: this.expectedIssuer
|
|
42
|
+
},
|
|
43
|
+
(err, decoded) => {
|
|
44
|
+
if (err) {
|
|
45
|
+
reject(err);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
resolve(decoded);
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
verifyNextAuthToken(token) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
if (!this.config.nextAuthSecret) {
|
|
56
|
+
reject(new Error("NextAuth secret is required for NextAuth token verification"));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
jwt.verify(
|
|
60
|
+
token,
|
|
61
|
+
this.config.nextAuthSecret,
|
|
62
|
+
{
|
|
63
|
+
algorithms: ["HS256"]
|
|
64
|
+
},
|
|
65
|
+
(err, decoded) => {
|
|
66
|
+
if (err) {
|
|
67
|
+
reject(err);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
resolve(decoded);
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async authenticate(req, res) {
|
|
76
|
+
const token = this.extractBearerToken(req);
|
|
77
|
+
if (!token) {
|
|
78
|
+
return { isAuthenticated: false };
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
if (this.config.nextAuthSecret) {
|
|
82
|
+
try {
|
|
83
|
+
const payload2 = await this.verifyNextAuthToken(token);
|
|
84
|
+
if (payload2.sub) {
|
|
85
|
+
return {
|
|
86
|
+
isAuthenticated: true,
|
|
87
|
+
userId: payload2.sub
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const payload = await this.verifyAzureADToken(token);
|
|
94
|
+
if (!payload.oid && !payload.sub) {
|
|
95
|
+
console.error("M365 auth verification failed: Token does not contain oid or sub claim");
|
|
96
|
+
return { isAuthenticated: false };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
isAuthenticated: true,
|
|
100
|
+
userId: payload.oid || payload.sub
|
|
101
|
+
};
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error("M365 auth verification failed:", err.message);
|
|
104
|
+
return { isAuthenticated: false };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
extractBearerToken(req) {
|
|
108
|
+
const authHeader = req.headers.authorization;
|
|
109
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return authHeader.substring(7);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
M365Auth
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=chunk-D55IH5LY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../implements/m365.auth.ts"],"sourcesContent":["import { BaseAuth } from \"@ainetwork/adk/modules\";\nimport { AuthResponse } from \"@ainetwork/adk/types/auth\";\nimport type { Request } from \"express\";\nimport jwt, { JwtHeader, SigningKeyCallback } from \"jsonwebtoken\";\nimport jwksClient from \"jwks-rsa\";\n\nexport interface M365AuthConfig {\n clientId: string;\n tenantId: string;\n cloudInstance?: string;\n nextAuthSecret?: string;\n}\n\ninterface AzureADTokenPayload {\n aud: string;\n iss: string;\n iat: number;\n nbf: number;\n exp: number;\n oid?: string;\n sub?: string;\n tid?: string;\n preferred_username?: string;\n email?: string;\n name?: string;\n}\n\ninterface NextAuthJWTPayload {\n name?: string;\n email?: string;\n picture?: string;\n sub: string;\n iat: number;\n exp: number;\n jti?: string;\n}\n\nexport class M365Auth extends BaseAuth {\n private readonly jwksClient: jwksClient.JwksClient;\n private readonly cloudInstance: string;\n private readonly expectedIssuer: string;\n\n constructor(private readonly config: M365AuthConfig) {\n super();\n this.cloudInstance = this.config.cloudInstance || \"https://login.microsoftonline.com\";\n this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;\n\n this.jwksClient = jwksClient({\n jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,\n cache: true,\n cacheMaxAge: 86400000, // 24 hours\n rateLimit: true,\n jwksRequestsPerMinute: 10,\n });\n }\n\n private getSigningKey = (header: JwtHeader, callback: SigningKeyCallback): void => {\n this.jwksClient.getSigningKey(header.kid, (err, key) => {\n if (err) {\n callback(err);\n return;\n }\n const signingKey = key?.getPublicKey();\n callback(null, signingKey);\n });\n };\n\n private verifyAzureADToken(token: string): Promise<AzureADTokenPayload> {\n return new Promise((resolve, reject) => {\n jwt.verify(\n token,\n this.getSigningKey,\n {\n algorithms: [\"RS256\"],\n audience: this.config.clientId,\n issuer: this.expectedIssuer,\n },\n (err: Error | null, decoded: unknown) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(decoded as AzureADTokenPayload);\n }\n );\n });\n }\n\n private verifyNextAuthToken(token: string): Promise<NextAuthJWTPayload> {\n return new Promise((resolve, reject) => {\n if (!this.config.nextAuthSecret) {\n reject(new Error(\"NextAuth secret is required for NextAuth token verification\"));\n return;\n }\n\n jwt.verify(\n token,\n this.config.nextAuthSecret,\n {\n algorithms: [\"HS256\"],\n },\n (err: Error | null, decoded: unknown) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(decoded as NextAuthJWTPayload);\n }\n );\n });\n }\n\n public async authenticate(req: any, res: any): Promise<AuthResponse> {\n const token = this.extractBearerToken(req);\n if (!token) {\n return { isAuthenticated: false };\n }\n\n try {\n // First, try to verify as NextAuth JWT token (signed with NEXTAUTH_SECRET)\n if (this.config.nextAuthSecret) {\n try {\n const payload = await this.verifyNextAuthToken(token);\n if (payload.sub) {\n return {\n isAuthenticated: true,\n userId: payload.sub,\n };\n }\n } catch {\n // If NextAuth verification fails, try Azure AD token verification\n }\n }\n\n // Try to verify as Azure AD token\n const payload = await this.verifyAzureADToken(token);\n\n if (!payload.oid && !payload.sub) {\n console.error(\"M365 auth verification failed: Token does not contain oid or sub claim\");\n return { isAuthenticated: false };\n }\n\n return {\n isAuthenticated: true,\n userId: (payload.oid || payload.sub) as string,\n };\n } catch (err) {\n console.error(\"M365 auth verification failed:\", (err as Error).message);\n return { isAuthenticated: false };\n }\n }\n\n private extractBearerToken(req: Request): string | null {\n const authHeader = req.headers.authorization;\n if (!authHeader?.startsWith(\"Bearer \")) {\n return null;\n }\n return authHeader.substring(7);\n }\n}\n"],"mappings":"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","names":["payload"]}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// implements/m365.auth.ts
|
|
31
|
+
var m365_auth_exports = {};
|
|
32
|
+
__export(m365_auth_exports, {
|
|
33
|
+
M365Auth: () => M365Auth
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(m365_auth_exports);
|
|
36
|
+
var import_modules = require("@ainetwork/adk/modules");
|
|
37
|
+
var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
|
|
38
|
+
var import_jwks_rsa = __toESM(require("jwks-rsa"), 1);
|
|
39
|
+
var M365Auth = class extends import_modules.BaseAuth {
|
|
40
|
+
constructor(config) {
|
|
41
|
+
super();
|
|
42
|
+
this.config = config;
|
|
43
|
+
this.cloudInstance = this.config.cloudInstance || "https://login.microsoftonline.com";
|
|
44
|
+
this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;
|
|
45
|
+
this.jwksClient = (0, import_jwks_rsa.default)({
|
|
46
|
+
jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,
|
|
47
|
+
cache: true,
|
|
48
|
+
cacheMaxAge: 864e5,
|
|
49
|
+
// 24 hours
|
|
50
|
+
rateLimit: true,
|
|
51
|
+
jwksRequestsPerMinute: 10
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
jwksClient;
|
|
55
|
+
cloudInstance;
|
|
56
|
+
expectedIssuer;
|
|
57
|
+
getSigningKey = (header, callback) => {
|
|
58
|
+
this.jwksClient.getSigningKey(header.kid, (err, key) => {
|
|
59
|
+
if (err) {
|
|
60
|
+
callback(err);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const signingKey = key?.getPublicKey();
|
|
64
|
+
callback(null, signingKey);
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
verifyAzureADToken(token) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
import_jsonwebtoken.default.verify(
|
|
70
|
+
token,
|
|
71
|
+
this.getSigningKey,
|
|
72
|
+
{
|
|
73
|
+
algorithms: ["RS256"],
|
|
74
|
+
audience: this.config.clientId,
|
|
75
|
+
issuer: this.expectedIssuer
|
|
76
|
+
},
|
|
77
|
+
(err, decoded) => {
|
|
78
|
+
if (err) {
|
|
79
|
+
reject(err);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
resolve(decoded);
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
verifyNextAuthToken(token) {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
if (!this.config.nextAuthSecret) {
|
|
90
|
+
reject(new Error("NextAuth secret is required for NextAuth token verification"));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
import_jsonwebtoken.default.verify(
|
|
94
|
+
token,
|
|
95
|
+
this.config.nextAuthSecret,
|
|
96
|
+
{
|
|
97
|
+
algorithms: ["HS256"]
|
|
98
|
+
},
|
|
99
|
+
(err, decoded) => {
|
|
100
|
+
if (err) {
|
|
101
|
+
reject(err);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
resolve(decoded);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async authenticate(req, res) {
|
|
110
|
+
const token = this.extractBearerToken(req);
|
|
111
|
+
if (!token) {
|
|
112
|
+
return { isAuthenticated: false };
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
if (this.config.nextAuthSecret) {
|
|
116
|
+
try {
|
|
117
|
+
const payload2 = await this.verifyNextAuthToken(token);
|
|
118
|
+
if (payload2.sub) {
|
|
119
|
+
return {
|
|
120
|
+
isAuthenticated: true,
|
|
121
|
+
userId: payload2.sub
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const payload = await this.verifyAzureADToken(token);
|
|
128
|
+
if (!payload.oid && !payload.sub) {
|
|
129
|
+
console.error("M365 auth verification failed: Token does not contain oid or sub claim");
|
|
130
|
+
return { isAuthenticated: false };
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
isAuthenticated: true,
|
|
134
|
+
userId: payload.oid || payload.sub
|
|
135
|
+
};
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error("M365 auth verification failed:", err.message);
|
|
138
|
+
return { isAuthenticated: false };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
extractBearerToken(req) {
|
|
142
|
+
const authHeader = req.headers.authorization;
|
|
143
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return authHeader.substring(7);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
150
|
+
0 && (module.exports = {
|
|
151
|
+
M365Auth
|
|
152
|
+
});
|
|
153
|
+
//# sourceMappingURL=m365.auth.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../implements/m365.auth.ts"],"sourcesContent":["import { BaseAuth } from \"@ainetwork/adk/modules\";\nimport { AuthResponse } from \"@ainetwork/adk/types/auth\";\nimport type { Request } from \"express\";\nimport jwt, { JwtHeader, SigningKeyCallback } from \"jsonwebtoken\";\nimport jwksClient from \"jwks-rsa\";\n\nexport interface M365AuthConfig {\n clientId: string;\n tenantId: string;\n cloudInstance?: string;\n nextAuthSecret?: string;\n}\n\ninterface AzureADTokenPayload {\n aud: string;\n iss: string;\n iat: number;\n nbf: number;\n exp: number;\n oid?: string;\n sub?: string;\n tid?: string;\n preferred_username?: string;\n email?: string;\n name?: string;\n}\n\ninterface NextAuthJWTPayload {\n name?: string;\n email?: string;\n picture?: string;\n sub: string;\n iat: number;\n exp: number;\n jti?: string;\n}\n\nexport class M365Auth extends BaseAuth {\n private readonly jwksClient: jwksClient.JwksClient;\n private readonly cloudInstance: string;\n private readonly expectedIssuer: string;\n\n constructor(private readonly config: M365AuthConfig) {\n super();\n this.cloudInstance = this.config.cloudInstance || \"https://login.microsoftonline.com\";\n this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;\n\n this.jwksClient = jwksClient({\n jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,\n cache: true,\n cacheMaxAge: 86400000, // 24 hours\n rateLimit: true,\n jwksRequestsPerMinute: 10,\n });\n }\n\n private getSigningKey = (header: JwtHeader, callback: SigningKeyCallback): void => {\n this.jwksClient.getSigningKey(header.kid, (err, key) => {\n if (err) {\n callback(err);\n return;\n }\n const signingKey = key?.getPublicKey();\n callback(null, signingKey);\n });\n };\n\n private verifyAzureADToken(token: string): Promise<AzureADTokenPayload> {\n return new Promise((resolve, reject) => {\n jwt.verify(\n token,\n this.getSigningKey,\n {\n algorithms: [\"RS256\"],\n audience: this.config.clientId,\n issuer: this.expectedIssuer,\n },\n (err: Error | null, decoded: unknown) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(decoded as AzureADTokenPayload);\n }\n );\n });\n }\n\n private verifyNextAuthToken(token: string): Promise<NextAuthJWTPayload> {\n return new Promise((resolve, reject) => {\n if (!this.config.nextAuthSecret) {\n reject(new Error(\"NextAuth secret is required for NextAuth token verification\"));\n return;\n }\n\n jwt.verify(\n token,\n this.config.nextAuthSecret,\n {\n algorithms: [\"HS256\"],\n },\n (err: Error | null, decoded: unknown) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(decoded as NextAuthJWTPayload);\n }\n );\n });\n }\n\n public async authenticate(req: any, res: any): Promise<AuthResponse> {\n const token = this.extractBearerToken(req);\n if (!token) {\n return { isAuthenticated: false };\n }\n\n try {\n // First, try to verify as NextAuth JWT token (signed with NEXTAUTH_SECRET)\n if (this.config.nextAuthSecret) {\n try {\n const payload = await this.verifyNextAuthToken(token);\n if (payload.sub) {\n return {\n isAuthenticated: true,\n userId: payload.sub,\n };\n }\n } catch {\n // If NextAuth verification fails, try Azure AD token verification\n }\n }\n\n // Try to verify as Azure AD token\n const payload = await this.verifyAzureADToken(token);\n\n if (!payload.oid && !payload.sub) {\n console.error(\"M365 auth verification failed: Token does not contain oid or sub claim\");\n return { isAuthenticated: false };\n }\n\n return {\n isAuthenticated: true,\n userId: (payload.oid || payload.sub) as string,\n };\n } catch (err) {\n console.error(\"M365 auth verification failed:\", (err as Error).message);\n return { isAuthenticated: false };\n }\n }\n\n private extractBearerToken(req: Request): string | null {\n const authHeader = req.headers.authorization;\n if (!authHeader?.startsWith(\"Bearer \")) {\n return null;\n }\n return authHeader.substring(7);\n }\n}\n"],"mappings":"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","names":["jwksClient","jwt","payload"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseAuth } from '@ainetwork/adk/modules';
|
|
2
|
+
import { AuthResponse } from '@ainetwork/adk/types/auth';
|
|
3
|
+
|
|
4
|
+
interface M365AuthConfig {
|
|
5
|
+
clientId: string;
|
|
6
|
+
tenantId: string;
|
|
7
|
+
cloudInstance?: string;
|
|
8
|
+
nextAuthSecret?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class M365Auth extends BaseAuth {
|
|
11
|
+
private readonly config;
|
|
12
|
+
private readonly jwksClient;
|
|
13
|
+
private readonly cloudInstance;
|
|
14
|
+
private readonly expectedIssuer;
|
|
15
|
+
constructor(config: M365AuthConfig);
|
|
16
|
+
private getSigningKey;
|
|
17
|
+
private verifyAzureADToken;
|
|
18
|
+
private verifyNextAuthToken;
|
|
19
|
+
authenticate(req: any, res: any): Promise<AuthResponse>;
|
|
20
|
+
private extractBearerToken;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { M365Auth, type M365AuthConfig };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseAuth } from '@ainetwork/adk/modules';
|
|
2
|
+
import { AuthResponse } from '@ainetwork/adk/types/auth';
|
|
3
|
+
|
|
4
|
+
interface M365AuthConfig {
|
|
5
|
+
clientId: string;
|
|
6
|
+
tenantId: string;
|
|
7
|
+
cloudInstance?: string;
|
|
8
|
+
nextAuthSecret?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class M365Auth extends BaseAuth {
|
|
11
|
+
private readonly config;
|
|
12
|
+
private readonly jwksClient;
|
|
13
|
+
private readonly cloudInstance;
|
|
14
|
+
private readonly expectedIssuer;
|
|
15
|
+
constructor(config: M365AuthConfig);
|
|
16
|
+
private getSigningKey;
|
|
17
|
+
private verifyAzureADToken;
|
|
18
|
+
private verifyNextAuthToken;
|
|
19
|
+
authenticate(req: any, res: any): Promise<AuthResponse>;
|
|
20
|
+
private extractBearerToken;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { M365Auth, type M365AuthConfig };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
M365Auth: () => M365Auth
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// implements/m365.auth.ts
|
|
38
|
+
var import_modules = require("@ainetwork/adk/modules");
|
|
39
|
+
var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
|
|
40
|
+
var import_jwks_rsa = __toESM(require("jwks-rsa"), 1);
|
|
41
|
+
var M365Auth = class extends import_modules.BaseAuth {
|
|
42
|
+
constructor(config) {
|
|
43
|
+
super();
|
|
44
|
+
this.config = config;
|
|
45
|
+
this.cloudInstance = this.config.cloudInstance || "https://login.microsoftonline.com";
|
|
46
|
+
this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;
|
|
47
|
+
this.jwksClient = (0, import_jwks_rsa.default)({
|
|
48
|
+
jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,
|
|
49
|
+
cache: true,
|
|
50
|
+
cacheMaxAge: 864e5,
|
|
51
|
+
// 24 hours
|
|
52
|
+
rateLimit: true,
|
|
53
|
+
jwksRequestsPerMinute: 10
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
jwksClient;
|
|
57
|
+
cloudInstance;
|
|
58
|
+
expectedIssuer;
|
|
59
|
+
getSigningKey = (header, callback) => {
|
|
60
|
+
this.jwksClient.getSigningKey(header.kid, (err, key) => {
|
|
61
|
+
if (err) {
|
|
62
|
+
callback(err);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const signingKey = key?.getPublicKey();
|
|
66
|
+
callback(null, signingKey);
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
verifyAzureADToken(token) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
import_jsonwebtoken.default.verify(
|
|
72
|
+
token,
|
|
73
|
+
this.getSigningKey,
|
|
74
|
+
{
|
|
75
|
+
algorithms: ["RS256"],
|
|
76
|
+
audience: this.config.clientId,
|
|
77
|
+
issuer: this.expectedIssuer
|
|
78
|
+
},
|
|
79
|
+
(err, decoded) => {
|
|
80
|
+
if (err) {
|
|
81
|
+
reject(err);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
resolve(decoded);
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
verifyNextAuthToken(token) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
if (!this.config.nextAuthSecret) {
|
|
92
|
+
reject(new Error("NextAuth secret is required for NextAuth token verification"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
import_jsonwebtoken.default.verify(
|
|
96
|
+
token,
|
|
97
|
+
this.config.nextAuthSecret,
|
|
98
|
+
{
|
|
99
|
+
algorithms: ["HS256"]
|
|
100
|
+
},
|
|
101
|
+
(err, decoded) => {
|
|
102
|
+
if (err) {
|
|
103
|
+
reject(err);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
resolve(decoded);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async authenticate(req, res) {
|
|
112
|
+
const token = this.extractBearerToken(req);
|
|
113
|
+
if (!token) {
|
|
114
|
+
return { isAuthenticated: false };
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
if (this.config.nextAuthSecret) {
|
|
118
|
+
try {
|
|
119
|
+
const payload2 = await this.verifyNextAuthToken(token);
|
|
120
|
+
if (payload2.sub) {
|
|
121
|
+
return {
|
|
122
|
+
isAuthenticated: true,
|
|
123
|
+
userId: payload2.sub
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const payload = await this.verifyAzureADToken(token);
|
|
130
|
+
if (!payload.oid && !payload.sub) {
|
|
131
|
+
console.error("M365 auth verification failed: Token does not contain oid or sub claim");
|
|
132
|
+
return { isAuthenticated: false };
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
isAuthenticated: true,
|
|
136
|
+
userId: payload.oid || payload.sub
|
|
137
|
+
};
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.error("M365 auth verification failed:", err.message);
|
|
140
|
+
return { isAuthenticated: false };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
extractBearerToken(req) {
|
|
144
|
+
const authHeader = req.headers.authorization;
|
|
145
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return authHeader.substring(7);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
152
|
+
0 && (module.exports = {
|
|
153
|
+
M365Auth
|
|
154
|
+
});
|
|
155
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../index.ts","../implements/m365.auth.ts"],"sourcesContent":["export { M365Auth, type M365AuthConfig } from \"./implements/m365.auth\";\n","import { BaseAuth } from \"@ainetwork/adk/modules\";\nimport { AuthResponse } from \"@ainetwork/adk/types/auth\";\nimport type { Request } from \"express\";\nimport jwt, { JwtHeader, SigningKeyCallback } from \"jsonwebtoken\";\nimport jwksClient from \"jwks-rsa\";\n\nexport interface M365AuthConfig {\n clientId: string;\n tenantId: string;\n cloudInstance?: string;\n nextAuthSecret?: string;\n}\n\ninterface AzureADTokenPayload {\n aud: string;\n iss: string;\n iat: number;\n nbf: number;\n exp: number;\n oid?: string;\n sub?: string;\n tid?: string;\n preferred_username?: string;\n email?: string;\n name?: string;\n}\n\ninterface NextAuthJWTPayload {\n name?: string;\n email?: string;\n picture?: string;\n sub: string;\n iat: number;\n exp: number;\n jti?: string;\n}\n\nexport class M365Auth extends BaseAuth {\n private readonly jwksClient: jwksClient.JwksClient;\n private readonly cloudInstance: string;\n private readonly expectedIssuer: string;\n\n constructor(private readonly config: M365AuthConfig) {\n super();\n this.cloudInstance = this.config.cloudInstance || \"https://login.microsoftonline.com\";\n this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;\n\n this.jwksClient = jwksClient({\n jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,\n cache: true,\n cacheMaxAge: 86400000, // 24 hours\n rateLimit: true,\n jwksRequestsPerMinute: 10,\n });\n }\n\n private getSigningKey = (header: JwtHeader, callback: SigningKeyCallback): void => {\n this.jwksClient.getSigningKey(header.kid, (err, key) => {\n if (err) {\n callback(err);\n return;\n }\n const signingKey = key?.getPublicKey();\n callback(null, signingKey);\n });\n };\n\n private verifyAzureADToken(token: string): Promise<AzureADTokenPayload> {\n return new Promise((resolve, reject) => {\n jwt.verify(\n token,\n this.getSigningKey,\n {\n algorithms: [\"RS256\"],\n audience: this.config.clientId,\n issuer: this.expectedIssuer,\n },\n (err: Error | null, decoded: unknown) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(decoded as AzureADTokenPayload);\n }\n );\n });\n }\n\n private verifyNextAuthToken(token: string): Promise<NextAuthJWTPayload> {\n return new Promise((resolve, reject) => {\n if (!this.config.nextAuthSecret) {\n reject(new Error(\"NextAuth secret is required for NextAuth token verification\"));\n return;\n }\n\n jwt.verify(\n token,\n this.config.nextAuthSecret,\n {\n algorithms: [\"HS256\"],\n },\n (err: Error | null, decoded: unknown) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(decoded as NextAuthJWTPayload);\n }\n );\n });\n }\n\n public async authenticate(req: any, res: any): Promise<AuthResponse> {\n const token = this.extractBearerToken(req);\n if (!token) {\n return { isAuthenticated: false };\n }\n\n try {\n // First, try to verify as NextAuth JWT token (signed with NEXTAUTH_SECRET)\n if (this.config.nextAuthSecret) {\n try {\n const payload = await this.verifyNextAuthToken(token);\n if (payload.sub) {\n return {\n isAuthenticated: true,\n userId: payload.sub,\n };\n }\n } catch {\n // If NextAuth verification fails, try Azure AD token verification\n }\n }\n\n // Try to verify as Azure AD token\n const payload = await this.verifyAzureADToken(token);\n\n if (!payload.oid && !payload.sub) {\n console.error(\"M365 auth verification failed: Token does not contain oid or sub claim\");\n return { isAuthenticated: false };\n }\n\n return {\n isAuthenticated: true,\n userId: (payload.oid || payload.sub) as string,\n };\n } catch (err) {\n console.error(\"M365 auth verification failed:\", (err as Error).message);\n return { isAuthenticated: false };\n }\n }\n\n private extractBearerToken(req: Request): string | null {\n const authHeader = req.headers.authorization;\n if (!authHeader?.startsWith(\"Bearer \")) {\n return null;\n }\n return authHeader.substring(7);\n }\n}\n"],"mappings":"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","names":["jwksClient","jwt","payload"]}
|
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { BaseAuth } from "@ainetwork/adk/modules";
|
|
2
|
+
import { AuthResponse } from "@ainetwork/adk/types/auth";
|
|
3
|
+
import type { Request } from "express";
|
|
4
|
+
import jwt, { JwtHeader, SigningKeyCallback } from "jsonwebtoken";
|
|
5
|
+
import jwksClient from "jwks-rsa";
|
|
6
|
+
|
|
7
|
+
export interface M365AuthConfig {
|
|
8
|
+
clientId: string;
|
|
9
|
+
tenantId: string;
|
|
10
|
+
cloudInstance?: string;
|
|
11
|
+
nextAuthSecret?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface AzureADTokenPayload {
|
|
15
|
+
aud: string;
|
|
16
|
+
iss: string;
|
|
17
|
+
iat: number;
|
|
18
|
+
nbf: number;
|
|
19
|
+
exp: number;
|
|
20
|
+
oid?: string;
|
|
21
|
+
sub?: string;
|
|
22
|
+
tid?: string;
|
|
23
|
+
preferred_username?: string;
|
|
24
|
+
email?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface NextAuthJWTPayload {
|
|
29
|
+
name?: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
picture?: string;
|
|
32
|
+
sub: string;
|
|
33
|
+
iat: number;
|
|
34
|
+
exp: number;
|
|
35
|
+
jti?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class M365Auth extends BaseAuth {
|
|
39
|
+
private readonly jwksClient: jwksClient.JwksClient;
|
|
40
|
+
private readonly cloudInstance: string;
|
|
41
|
+
private readonly expectedIssuer: string;
|
|
42
|
+
|
|
43
|
+
constructor(private readonly config: M365AuthConfig) {
|
|
44
|
+
super();
|
|
45
|
+
this.cloudInstance = this.config.cloudInstance || "https://login.microsoftonline.com";
|
|
46
|
+
this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;
|
|
47
|
+
|
|
48
|
+
this.jwksClient = jwksClient({
|
|
49
|
+
jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,
|
|
50
|
+
cache: true,
|
|
51
|
+
cacheMaxAge: 86400000, // 24 hours
|
|
52
|
+
rateLimit: true,
|
|
53
|
+
jwksRequestsPerMinute: 10,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private getSigningKey = (header: JwtHeader, callback: SigningKeyCallback): void => {
|
|
58
|
+
this.jwksClient.getSigningKey(header.kid, (err, key) => {
|
|
59
|
+
if (err) {
|
|
60
|
+
callback(err);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const signingKey = key?.getPublicKey();
|
|
64
|
+
callback(null, signingKey);
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
private verifyAzureADToken(token: string): Promise<AzureADTokenPayload> {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
jwt.verify(
|
|
71
|
+
token,
|
|
72
|
+
this.getSigningKey,
|
|
73
|
+
{
|
|
74
|
+
algorithms: ["RS256"],
|
|
75
|
+
audience: this.config.clientId,
|
|
76
|
+
issuer: this.expectedIssuer,
|
|
77
|
+
},
|
|
78
|
+
(err: Error | null, decoded: unknown) => {
|
|
79
|
+
if (err) {
|
|
80
|
+
reject(err);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
resolve(decoded as AzureADTokenPayload);
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private verifyNextAuthToken(token: string): Promise<NextAuthJWTPayload> {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
if (!this.config.nextAuthSecret) {
|
|
92
|
+
reject(new Error("NextAuth secret is required for NextAuth token verification"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
jwt.verify(
|
|
97
|
+
token,
|
|
98
|
+
this.config.nextAuthSecret,
|
|
99
|
+
{
|
|
100
|
+
algorithms: ["HS256"],
|
|
101
|
+
},
|
|
102
|
+
(err: Error | null, decoded: unknown) => {
|
|
103
|
+
if (err) {
|
|
104
|
+
reject(err);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
resolve(decoded as NextAuthJWTPayload);
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public async authenticate(req: any, res: any): Promise<AuthResponse> {
|
|
114
|
+
const token = this.extractBearerToken(req);
|
|
115
|
+
if (!token) {
|
|
116
|
+
return { isAuthenticated: false };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// First, try to verify as NextAuth JWT token (signed with NEXTAUTH_SECRET)
|
|
121
|
+
if (this.config.nextAuthSecret) {
|
|
122
|
+
try {
|
|
123
|
+
const payload = await this.verifyNextAuthToken(token);
|
|
124
|
+
if (payload.sub) {
|
|
125
|
+
return {
|
|
126
|
+
isAuthenticated: true,
|
|
127
|
+
userId: payload.sub,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// If NextAuth verification fails, try Azure AD token verification
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Try to verify as Azure AD token
|
|
136
|
+
const payload = await this.verifyAzureADToken(token);
|
|
137
|
+
|
|
138
|
+
if (!payload.oid && !payload.sub) {
|
|
139
|
+
console.error("M365 auth verification failed: Token does not contain oid or sub claim");
|
|
140
|
+
return { isAuthenticated: false };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
isAuthenticated: true,
|
|
145
|
+
userId: (payload.oid || payload.sub) as string,
|
|
146
|
+
};
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error("M365 auth verification failed:", (err as Error).message);
|
|
149
|
+
return { isAuthenticated: false };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private extractBearerToken(req: Request): string | null {
|
|
154
|
+
const authHeader = req.headers.authorization;
|
|
155
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return authHeader.substring(7);
|
|
159
|
+
}
|
|
160
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { M365Auth, type M365AuthConfig } from "./implements/m365.auth";
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ainetwork/adk-provider-auth-m365",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"author": "AI Network (https://ainetwork.ai)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.cjs",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"require": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"clean": "rm -rf dist"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@ainetwork/adk": "^0.3.0",
|
|
25
|
+
"jsonwebtoken": "^9.0.2",
|
|
26
|
+
"jwks-rsa": "^3.1.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/express": "^4.17.21",
|
|
30
|
+
"@types/jsonwebtoken": "^9.0.7",
|
|
31
|
+
"typescript": "^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"gitHead": "741d325b4e2de45b8fe45e11d6a525d6467f5ab6"
|
|
38
|
+
}
|
package/tsconfig.json
ADDED