@ainetwork/adk-provider-auth-m365 0.7.0 → 0.7.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.
@@ -92,7 +92,8 @@ var M365Auth = class extends AuthModule {
92
92
  if (payload2.sub) {
93
93
  return {
94
94
  isAuthenticated: true,
95
- userId: payload2.sub
95
+ userId: payload2.sub,
96
+ email: payload2.userPrincipalName || payload2.email
96
97
  };
97
98
  }
98
99
  } catch {
@@ -105,7 +106,10 @@ var M365Auth = class extends AuthModule {
105
106
  }
106
107
  return {
107
108
  isAuthenticated: true,
108
- userId: payload.oid || payload.sub
109
+ userId: payload.oid || payload.sub,
110
+ // userPrincipalName carries the email. Use it directly if present,
111
+ // else the UPN-equivalent claim (preferred_username v2.0 / upn v1.0), else email.
112
+ email: payload.userPrincipalName || payload.preferred_username || payload.upn || payload.email
109
113
  };
110
114
  } catch (err) {
111
115
  console.error("M365 auth verification failed:", err.message);
@@ -124,4 +128,4 @@ var M365Auth = class extends AuthModule {
124
128
  export {
125
129
  M365Auth
126
130
  };
127
- //# sourceMappingURL=chunk-4LXYNIZV.js.map
131
+ //# sourceMappingURL=chunk-O2NTTHIX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../implements/m365.auth.ts"],"sourcesContent":["import { AuthModule } 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 userPrincipalName?: string; // explicit UPN claim when present\n upn?: string; // userPrincipalName (Azure AD v1.0 tokens)\n preferred_username?: string; // UPN-equivalent in v2.0 tokens\n email?: string;\n name?: string;\n}\n\ninterface NextAuthJWTPayload {\n name?: string;\n email?: string;\n userPrincipalName?: string; // set by the WISE NextAuth jwt callback\n picture?: string;\n sub: string;\n iat: number;\n exp: number;\n jti?: string;\n}\n\nexport class M365Auth extends AuthModule {\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 email: payload.userPrincipalName || payload.email,\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 // userPrincipalName carries the email. Use it directly if present,\n // else the UPN-equivalent claim (preferred_username v2.0 / upn v1.0), else email.\n email: payload.userPrincipalName || payload.preferred_username || payload.upn || payload.email,\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,SAAS,kBAAkB;AAG3B,OAAO,SAA4C;AACnD,OAAO,gBAAgB;AAsChB,IAAM,WAAN,cAAuB,WAAW;AAAA,EAKvC,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,aAAa,WAAW;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,UAAI;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,UAAI;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,gBAAMA,WAAU,MAAM,KAAK,oBAAoB,KAAK;AACpD,cAAIA,SAAQ,KAAK;AACf,mBAAO;AAAA,cACL,iBAAiB;AAAA,cACjB,QAAQA,SAAQ;AAAA,cAChB,OAAOA,SAAQ,qBAAqBA,SAAQ;AAAA,YAC9C;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;AAAA;AAAA,QAGhC,OAAO,QAAQ,qBAAqB,QAAQ,sBAAsB,QAAQ,OAAO,QAAQ;AAAA,MAC3F;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":["payload"]}
@@ -126,7 +126,8 @@ var M365Auth = class extends import_modules.AuthModule {
126
126
  if (payload2.sub) {
127
127
  return {
128
128
  isAuthenticated: true,
129
- userId: payload2.sub
129
+ userId: payload2.sub,
130
+ email: payload2.userPrincipalName || payload2.email
130
131
  };
131
132
  }
132
133
  } catch {
@@ -139,7 +140,10 @@ var M365Auth = class extends import_modules.AuthModule {
139
140
  }
140
141
  return {
141
142
  isAuthenticated: true,
142
- userId: payload.oid || payload.sub
143
+ userId: payload.oid || payload.sub,
144
+ // userPrincipalName carries the email. Use it directly if present,
145
+ // else the UPN-equivalent claim (preferred_username v2.0 / upn v1.0), else email.
146
+ email: payload.userPrincipalName || payload.preferred_username || payload.upn || payload.email
143
147
  };
144
148
  } catch (err) {
145
149
  console.error("M365 auth verification failed:", err.message);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../implements/m365.auth.ts"],"sourcesContent":["import { AuthModule } 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 AuthModule {\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"]}
1
+ {"version":3,"sources":["../../implements/m365.auth.ts"],"sourcesContent":["import { AuthModule } 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 userPrincipalName?: string; // explicit UPN claim when present\n upn?: string; // userPrincipalName (Azure AD v1.0 tokens)\n preferred_username?: string; // UPN-equivalent in v2.0 tokens\n email?: string;\n name?: string;\n}\n\ninterface NextAuthJWTPayload {\n name?: string;\n email?: string;\n userPrincipalName?: string; // set by the WISE NextAuth jwt callback\n picture?: string;\n sub: string;\n iat: number;\n exp: number;\n jti?: string;\n}\n\nexport class M365Auth extends AuthModule {\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 email: payload.userPrincipalName || payload.email,\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 // userPrincipalName carries the email. Use it directly if present,\n // else the UPN-equivalent claim (preferred_username v2.0 / upn v1.0), else email.\n email: payload.userPrincipalName || payload.preferred_username || payload.upn || payload.email,\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,qBAA2B;AAG3B,0BAAmD;AACnD,sBAAuB;AAsChB,IAAM,WAAN,cAAuB,0BAAW;AAAA,EAKvC,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,cAChB,OAAOA,SAAQ,qBAAqBA,SAAQ;AAAA,YAC9C;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;AAAA;AAAA,QAGhC,OAAO,QAAQ,qBAAqB,QAAQ,sBAAsB,QAAQ,OAAO,QAAQ;AAAA,MAC3F;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"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  M365Auth
3
- } from "../chunk-4LXYNIZV.js";
3
+ } from "../chunk-O2NTTHIX.js";
4
4
  export {
5
5
  M365Auth
6
6
  };
package/dist/index.cjs CHANGED
@@ -128,7 +128,8 @@ var M365Auth = class extends import_modules.AuthModule {
128
128
  if (payload2.sub) {
129
129
  return {
130
130
  isAuthenticated: true,
131
- userId: payload2.sub
131
+ userId: payload2.sub,
132
+ email: payload2.userPrincipalName || payload2.email
132
133
  };
133
134
  }
134
135
  } catch {
@@ -141,7 +142,10 @@ var M365Auth = class extends import_modules.AuthModule {
141
142
  }
142
143
  return {
143
144
  isAuthenticated: true,
144
- userId: payload.oid || payload.sub
145
+ userId: payload.oid || payload.sub,
146
+ // userPrincipalName carries the email. Use it directly if present,
147
+ // else the UPN-equivalent claim (preferred_username v2.0 / upn v1.0), else email.
148
+ email: payload.userPrincipalName || payload.preferred_username || payload.upn || payload.email
145
149
  };
146
150
  } catch (err) {
147
151
  console.error("M365 auth verification failed:", err.message);
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts","../implements/m365.auth.ts"],"sourcesContent":["export { M365Auth, type M365AuthConfig } from \"./implements/m365.auth\";\n","import { AuthModule } 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 AuthModule {\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"]}
1
+ {"version":3,"sources":["../index.ts","../implements/m365.auth.ts"],"sourcesContent":["export { M365Auth, type M365AuthConfig } from \"./implements/m365.auth\";\n","import { AuthModule } 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 userPrincipalName?: string; // explicit UPN claim when present\n upn?: string; // userPrincipalName (Azure AD v1.0 tokens)\n preferred_username?: string; // UPN-equivalent in v2.0 tokens\n email?: string;\n name?: string;\n}\n\ninterface NextAuthJWTPayload {\n name?: string;\n email?: string;\n userPrincipalName?: string; // set by the WISE NextAuth jwt callback\n picture?: string;\n sub: string;\n iat: number;\n exp: number;\n jti?: string;\n}\n\nexport class M365Auth extends AuthModule {\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 email: payload.userPrincipalName || payload.email,\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 // userPrincipalName carries the email. Use it directly if present,\n // else the UPN-equivalent claim (preferred_username v2.0 / upn v1.0), else email.\n email: payload.userPrincipalName || payload.preferred_username || payload.upn || payload.email,\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-4LXYNIZV.js";
3
+ } from "./chunk-O2NTTHIX.js";
4
4
  export {
5
5
  M365Auth
6
6
  };
@@ -22,7 +22,9 @@ interface AzureADTokenPayload {
22
22
  tid?: string;
23
23
  appid?: string;
24
24
  azp?: string;
25
- preferred_username?: string;
25
+ userPrincipalName?: string; // explicit UPN claim when present
26
+ upn?: string; // userPrincipalName (Azure AD v1.0 tokens)
27
+ preferred_username?: string; // UPN-equivalent in v2.0 tokens
26
28
  email?: string;
27
29
  name?: string;
28
30
  }
@@ -30,6 +32,7 @@ interface AzureADTokenPayload {
30
32
  interface NextAuthJWTPayload {
31
33
  name?: string;
32
34
  email?: string;
35
+ userPrincipalName?: string; // set by the WISE NextAuth jwt callback
33
36
  picture?: string;
34
37
  sub: string;
35
38
  iat: number;
@@ -141,6 +144,7 @@ export class M365Auth extends AuthModule {
141
144
  return {
142
145
  isAuthenticated: true,
143
146
  userId: payload.sub,
147
+ email: payload.userPrincipalName || payload.email,
144
148
  };
145
149
  }
146
150
  } catch {
@@ -159,6 +163,9 @@ export class M365Auth extends AuthModule {
159
163
  return {
160
164
  isAuthenticated: true,
161
165
  userId: (payload.oid || payload.sub) as string,
166
+ // userPrincipalName carries the email. Use it directly if present,
167
+ // else the UPN-equivalent claim (preferred_username v2.0 / upn v1.0), else email.
168
+ email: payload.userPrincipalName || payload.preferred_username || payload.upn || payload.email,
162
169
  };
163
170
  } catch (err) {
164
171
  console.error("M365 auth verification failed:", (err as Error).message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainetwork/adk-provider-auth-m365",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "author": "AI Network (https://ainetwork.ai)",
5
5
  "type": "module",
6
6
  "engines": {
@@ -25,7 +25,7 @@
25
25
  "jwks-rsa": "^3.1.0"
26
26
  },
27
27
  "peerDependencies": {
28
- "@ainetwork/adk": "^0.7.0"
28
+ "@ainetwork/adk": "^0.7.2"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/express": "^4.17.21",
@@ -36,5 +36,5 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
- "gitHead": "39b90b146668f11b0b6a15a3ba0ab5bf80eb026f"
39
+ "gitHead": "86a3b76c8d83193988b70a11abcae1f0c05aa1e9"
40
40
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../implements/m365.auth.ts"],"sourcesContent":["import { AuthModule } 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 AuthModule {\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"]}