@ainetwork/adk-provider-auth-m365 0.3.0 → 0.3.2

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.
@@ -7,7 +7,10 @@ var M365Auth = class extends BaseAuth {
7
7
  super();
8
8
  this.config = config;
9
9
  this.cloudInstance = this.config.cloudInstance || "https://login.microsoftonline.com";
10
- this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;
10
+ this.expectedIssuers = [
11
+ `${this.cloudInstance}/${this.config.tenantId}/v2.0`,
12
+ `https://sts.windows.net/${this.config.tenantId}/`
13
+ ];
11
14
  this.jwksClient = jwksClient({
12
15
  jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,
13
16
  cache: true,
@@ -19,7 +22,7 @@ var M365Auth = class extends BaseAuth {
19
22
  }
20
23
  jwksClient;
21
24
  cloudInstance;
22
- expectedIssuer;
25
+ expectedIssuers;
23
26
  getSigningKey = (header, callback) => {
24
27
  this.jwksClient.getSigningKey(header.kid, (err, key) => {
25
28
  if (err) {
@@ -37,15 +40,20 @@ var M365Auth = class extends BaseAuth {
37
40
  this.getSigningKey,
38
41
  {
39
42
  algorithms: ["RS256"],
40
- audience: this.config.clientId,
41
- issuer: this.expectedIssuer
43
+ issuer: this.expectedIssuers
42
44
  },
43
45
  (err, decoded) => {
44
46
  if (err) {
45
47
  reject(err);
46
48
  return;
47
49
  }
48
- resolve(decoded);
50
+ const payload = decoded;
51
+ const appId = payload.appid || payload.azp;
52
+ if (payload.aud !== this.config.clientId && appId !== this.config.clientId) {
53
+ reject(new Error("Token was not issued for this client"));
54
+ return;
55
+ }
56
+ resolve(payload);
49
57
  }
50
58
  );
51
59
  });
@@ -116,4 +124,4 @@ var M365Auth = class extends BaseAuth {
116
124
  export {
117
125
  M365Auth
118
126
  };
119
- //# sourceMappingURL=chunk-D55IH5LY.js.map
127
+ //# sourceMappingURL=chunk-O6Z6MHKC.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 appid?: string;\n azp?: 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 expectedIssuers: [string, ...string[]];\n\n constructor(private readonly config: M365AuthConfig) {\n super();\n this.cloudInstance = this.config.cloudInstance || \"https://login.microsoftonline.com\";\n // Support both v1.0 and v2.0 issuers\n this.expectedIssuers = [\n `${this.cloudInstance}/${this.config.tenantId}/v2.0`,\n `https://sts.windows.net/${this.config.tenantId}/`,\n ] as [string, ...string[]];\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 issuer: this.expectedIssuers,\n },\n (err: Error | null, decoded: unknown) => {\n if (err) {\n reject(err);\n return;\n }\n\n const payload = decoded as AzureADTokenPayload;\n\n // For Access Tokens, verify appid or azp matches clientId\n // For ID Tokens, verify aud matches clientId\n const appId = payload.appid || payload.azp;\n if (payload.aud !== this.config.clientId && appId !== this.config.clientId) {\n reject(new Error(\"Token was not issued for this client\"));\n return;\n }\n\n resolve(payload);\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"]}
@@ -41,7 +41,10 @@ var M365Auth = class extends import_modules.BaseAuth {
41
41
  super();
42
42
  this.config = config;
43
43
  this.cloudInstance = this.config.cloudInstance || "https://login.microsoftonline.com";
44
- this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;
44
+ this.expectedIssuers = [
45
+ `${this.cloudInstance}/${this.config.tenantId}/v2.0`,
46
+ `https://sts.windows.net/${this.config.tenantId}/`
47
+ ];
45
48
  this.jwksClient = (0, import_jwks_rsa.default)({
46
49
  jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,
47
50
  cache: true,
@@ -53,7 +56,7 @@ var M365Auth = class extends import_modules.BaseAuth {
53
56
  }
54
57
  jwksClient;
55
58
  cloudInstance;
56
- expectedIssuer;
59
+ expectedIssuers;
57
60
  getSigningKey = (header, callback) => {
58
61
  this.jwksClient.getSigningKey(header.kid, (err, key) => {
59
62
  if (err) {
@@ -71,15 +74,20 @@ var M365Auth = class extends import_modules.BaseAuth {
71
74
  this.getSigningKey,
72
75
  {
73
76
  algorithms: ["RS256"],
74
- audience: this.config.clientId,
75
- issuer: this.expectedIssuer
77
+ issuer: this.expectedIssuers
76
78
  },
77
79
  (err, decoded) => {
78
80
  if (err) {
79
81
  reject(err);
80
82
  return;
81
83
  }
82
- resolve(decoded);
84
+ const payload = decoded;
85
+ const appId = payload.appid || payload.azp;
86
+ if (payload.aud !== this.config.clientId && appId !== this.config.clientId) {
87
+ reject(new Error("Token was not issued for this client"));
88
+ return;
89
+ }
90
+ resolve(payload);
83
91
  }
84
92
  );
85
93
  });
@@ -1 +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"]}
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 appid?: string;\n azp?: 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 expectedIssuers: [string, ...string[]];\n\n constructor(private readonly config: M365AuthConfig) {\n super();\n this.cloudInstance = this.config.cloudInstance || \"https://login.microsoftonline.com\";\n // Support both v1.0 and v2.0 issuers\n this.expectedIssuers = [\n `${this.cloudInstance}/${this.config.tenantId}/v2.0`,\n `https://sts.windows.net/${this.config.tenantId}/`,\n ] as [string, ...string[]];\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 issuer: this.expectedIssuers,\n },\n (err: Error | null, decoded: unknown) => {\n if (err) {\n reject(err);\n return;\n }\n\n const payload = decoded as AzureADTokenPayload;\n\n // For Access Tokens, verify appid or azp matches clientId\n // For ID Tokens, verify aud matches clientId\n const appId = payload.appid || payload.azp;\n if (payload.aud !== this.config.clientId && appId !== this.config.clientId) {\n reject(new Error(\"Token was not issued for this client\"));\n return;\n }\n\n resolve(payload);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAyB;AAGzB,0BAAmD;AACnD,sBAAuB;AAmChB,IAAM,WAAN,cAAuB,wBAAS;AAAA,EAKrC,YAA6B,QAAwB;AACnD,UAAM;AADqB;AAE3B,SAAK,gBAAgB,KAAK,OAAO,iBAAiB;AAElD,SAAK,kBAAkB;AAAA,MACrB,GAAG,KAAK,aAAa,IAAI,KAAK,OAAO,QAAQ;AAAA,MAC7C,2BAA2B,KAAK,OAAO,QAAQ;AAAA,IACjD;AAEA,SAAK,iBAAa,gBAAAA,SAAW;AAAA,MAC3B,SAAS,GAAG,KAAK,aAAa,IAAI,KAAK,OAAO,QAAQ;AAAA,MACtD,OAAO;AAAA,MACP,aAAa;AAAA;AAAA,MACb,WAAW;AAAA,MACX,uBAAuB;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EApBiB;AAAA,EACA;AAAA,EACA;AAAA,EAoBT,gBAAgB,CAAC,QAAmB,aAAuC;AACjF,SAAK,WAAW,cAAc,OAAO,KAAK,CAAC,KAAK,QAAQ;AACtD,UAAI,KAAK;AACP,iBAAS,GAAG;AACZ;AAAA,MACF;AACA,YAAM,aAAa,KAAK,aAAa;AACrC,eAAS,MAAM,UAAU;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEQ,mBAAmB,OAA6C;AACtE,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,0BAAAC,QAAI;AAAA,QACF;AAAA,QACA,KAAK;AAAA,QACL;AAAA,UACE,YAAY,CAAC,OAAO;AAAA,UACpB,QAAQ,KAAK;AAAA,QACf;AAAA,QACA,CAAC,KAAmB,YAAqB;AACvC,cAAI,KAAK;AACP,mBAAO,GAAG;AACV;AAAA,UACF;AAEA,gBAAM,UAAU;AAIhB,gBAAM,QAAQ,QAAQ,SAAS,QAAQ;AACvC,cAAI,QAAQ,QAAQ,KAAK,OAAO,YAAY,UAAU,KAAK,OAAO,UAAU;AAC1E,mBAAO,IAAI,MAAM,sCAAsC,CAAC;AACxD;AAAA,UACF;AAEA,kBAAQ,OAAO;AAAA,QACjB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,oBAAoB,OAA4C;AACtE,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,CAAC,KAAK,OAAO,gBAAgB;AAC/B,eAAO,IAAI,MAAM,6DAA6D,CAAC;AAC/E;AAAA,MACF;AAEA,0BAAAA,QAAI;AAAA,QACF;AAAA,QACA,KAAK,OAAO;AAAA,QACZ;AAAA,UACE,YAAY,CAAC,OAAO;AAAA,QACtB;AAAA,QACA,CAAC,KAAmB,YAAqB;AACvC,cAAI,KAAK;AACP,mBAAO,GAAG;AACV;AAAA,UACF;AACA,kBAAQ,OAA6B;AAAA,QACvC;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAa,aAAa,KAAU,KAAiC;AACnE,UAAM,QAAQ,KAAK,mBAAmB,GAAG;AACzC,QAAI,CAAC,OAAO;AACV,aAAO,EAAE,iBAAiB,MAAM;AAAA,IAClC;AAEA,QAAI;AAEF,UAAI,KAAK,OAAO,gBAAgB;AAC9B,YAAI;AACF,gBAAMC,WAAU,MAAM,KAAK,oBAAoB,KAAK;AACpD,cAAIA,SAAQ,KAAK;AACf,mBAAO;AAAA,cACL,iBAAiB;AAAA,cACjB,QAAQA,SAAQ;AAAA,YAClB;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAGA,YAAM,UAAU,MAAM,KAAK,mBAAmB,KAAK;AAEnD,UAAI,CAAC,QAAQ,OAAO,CAAC,QAAQ,KAAK;AAChC,gBAAQ,MAAM,wEAAwE;AACtF,eAAO,EAAE,iBAAiB,MAAM;AAAA,MAClC;AAEA,aAAO;AAAA,QACL,iBAAiB;AAAA,QACjB,QAAS,QAAQ,OAAO,QAAQ;AAAA,MAClC;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,kCAAmC,IAAc,OAAO;AACtE,aAAO,EAAE,iBAAiB,MAAM;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,mBAAmB,KAA6B;AACtD,UAAM,aAAa,IAAI,QAAQ;AAC/B,QAAI,CAAC,YAAY,WAAW,SAAS,GAAG;AACtC,aAAO;AAAA,IACT;AACA,WAAO,WAAW,UAAU,CAAC;AAAA,EAC/B;AACF;","names":["jwksClient","jwt","payload"]}
@@ -11,7 +11,7 @@ declare class M365Auth extends BaseAuth {
11
11
  private readonly config;
12
12
  private readonly jwksClient;
13
13
  private readonly cloudInstance;
14
- private readonly expectedIssuer;
14
+ private readonly expectedIssuers;
15
15
  constructor(config: M365AuthConfig);
16
16
  private getSigningKey;
17
17
  private verifyAzureADToken;
@@ -11,7 +11,7 @@ declare class M365Auth extends BaseAuth {
11
11
  private readonly config;
12
12
  private readonly jwksClient;
13
13
  private readonly cloudInstance;
14
- private readonly expectedIssuer;
14
+ private readonly expectedIssuers;
15
15
  constructor(config: M365AuthConfig);
16
16
  private getSigningKey;
17
17
  private verifyAzureADToken;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  M365Auth
3
- } from "../chunk-D55IH5LY.js";
3
+ } from "../chunk-O6Z6MHKC.js";
4
4
  export {
5
5
  M365Auth
6
6
  };
package/dist/index.cjs CHANGED
@@ -43,7 +43,10 @@ var M365Auth = class extends import_modules.BaseAuth {
43
43
  super();
44
44
  this.config = config;
45
45
  this.cloudInstance = this.config.cloudInstance || "https://login.microsoftonline.com";
46
- this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;
46
+ this.expectedIssuers = [
47
+ `${this.cloudInstance}/${this.config.tenantId}/v2.0`,
48
+ `https://sts.windows.net/${this.config.tenantId}/`
49
+ ];
47
50
  this.jwksClient = (0, import_jwks_rsa.default)({
48
51
  jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,
49
52
  cache: true,
@@ -55,7 +58,7 @@ var M365Auth = class extends import_modules.BaseAuth {
55
58
  }
56
59
  jwksClient;
57
60
  cloudInstance;
58
- expectedIssuer;
61
+ expectedIssuers;
59
62
  getSigningKey = (header, callback) => {
60
63
  this.jwksClient.getSigningKey(header.kid, (err, key) => {
61
64
  if (err) {
@@ -73,15 +76,20 @@ var M365Auth = class extends import_modules.BaseAuth {
73
76
  this.getSigningKey,
74
77
  {
75
78
  algorithms: ["RS256"],
76
- audience: this.config.clientId,
77
- issuer: this.expectedIssuer
79
+ issuer: this.expectedIssuers
78
80
  },
79
81
  (err, decoded) => {
80
82
  if (err) {
81
83
  reject(err);
82
84
  return;
83
85
  }
84
- resolve(decoded);
86
+ const payload = decoded;
87
+ const appId = payload.appid || payload.azp;
88
+ if (payload.aud !== this.config.clientId && appId !== this.config.clientId) {
89
+ reject(new Error("Token was not issued for this client"));
90
+ return;
91
+ }
92
+ resolve(payload);
85
93
  }
86
94
  );
87
95
  });
@@ -1 +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"]}
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 appid?: string;\n azp?: 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 expectedIssuers: [string, ...string[]];\n\n constructor(private readonly config: M365AuthConfig) {\n super();\n this.cloudInstance = this.config.cloudInstance || \"https://login.microsoftonline.com\";\n // Support both v1.0 and v2.0 issuers\n this.expectedIssuers = [\n `${this.cloudInstance}/${this.config.tenantId}/v2.0`,\n `https://sts.windows.net/${this.config.tenantId}/`,\n ] as [string, ...string[]];\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 issuer: this.expectedIssuers,\n },\n (err: Error | null, decoded: unknown) => {\n if (err) {\n reject(err);\n return;\n }\n\n const payload = decoded as AzureADTokenPayload;\n\n // For Access Tokens, verify appid or azp matches clientId\n // For ID Tokens, verify aud matches clientId\n const appId = payload.appid || payload.azp;\n if (payload.aud !== this.config.clientId && appId !== this.config.clientId) {\n reject(new Error(\"Token was not issued for this client\"));\n return;\n }\n\n resolve(payload);\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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  M365Auth
3
- } from "./chunk-D55IH5LY.js";
3
+ } from "./chunk-O6Z6MHKC.js";
4
4
  export {
5
5
  M365Auth
6
6
  };
@@ -20,6 +20,8 @@ interface AzureADTokenPayload {
20
20
  oid?: string;
21
21
  sub?: string;
22
22
  tid?: string;
23
+ appid?: string;
24
+ azp?: string;
23
25
  preferred_username?: string;
24
26
  email?: string;
25
27
  name?: string;
@@ -38,12 +40,16 @@ interface NextAuthJWTPayload {
38
40
  export class M365Auth extends BaseAuth {
39
41
  private readonly jwksClient: jwksClient.JwksClient;
40
42
  private readonly cloudInstance: string;
41
- private readonly expectedIssuer: string;
43
+ private readonly expectedIssuers: [string, ...string[]];
42
44
 
43
45
  constructor(private readonly config: M365AuthConfig) {
44
46
  super();
45
47
  this.cloudInstance = this.config.cloudInstance || "https://login.microsoftonline.com";
46
- this.expectedIssuer = `${this.cloudInstance}/${this.config.tenantId}/v2.0`;
48
+ // Support both v1.0 and v2.0 issuers
49
+ this.expectedIssuers = [
50
+ `${this.cloudInstance}/${this.config.tenantId}/v2.0`,
51
+ `https://sts.windows.net/${this.config.tenantId}/`,
52
+ ] as [string, ...string[]];
47
53
 
48
54
  this.jwksClient = jwksClient({
49
55
  jwksUri: `${this.cloudInstance}/${this.config.tenantId}/discovery/v2.0/keys`,
@@ -72,15 +78,25 @@ export class M365Auth extends BaseAuth {
72
78
  this.getSigningKey,
73
79
  {
74
80
  algorithms: ["RS256"],
75
- audience: this.config.clientId,
76
- issuer: this.expectedIssuer,
81
+ issuer: this.expectedIssuers,
77
82
  },
78
83
  (err: Error | null, decoded: unknown) => {
79
84
  if (err) {
80
85
  reject(err);
81
86
  return;
82
87
  }
83
- resolve(decoded as AzureADTokenPayload);
88
+
89
+ const payload = decoded as AzureADTokenPayload;
90
+
91
+ // For Access Tokens, verify appid or azp matches clientId
92
+ // For ID Tokens, verify aud matches clientId
93
+ const appId = payload.appid || payload.azp;
94
+ if (payload.aud !== this.config.clientId && appId !== this.config.clientId) {
95
+ reject(new Error("Token was not issued for this client"));
96
+ return;
97
+ }
98
+
99
+ resolve(payload);
84
100
  }
85
101
  );
86
102
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainetwork/adk-provider-auth-m365",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "author": "AI Network (https://ainetwork.ai)",
5
5
  "type": "module",
6
6
  "engines": {
@@ -21,7 +21,7 @@
21
21
  "clean": "rm -rf dist"
22
22
  },
23
23
  "dependencies": {
24
- "@ainetwork/adk": "^0.3.0",
24
+ "@ainetwork/adk": "^0.3.2",
25
25
  "jsonwebtoken": "^9.0.2",
26
26
  "jwks-rsa": "^3.1.0"
27
27
  },
@@ -34,5 +34,5 @@
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  },
37
- "gitHead": "741d325b4e2de45b8fe45e11d6a525d6467f5ab6"
37
+ "gitHead": "4bfa5afae29304e6cb5f106c7327f5f2092f6601"
38
38
  }
@@ -1 +0,0 @@
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"]}