mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 00:16:41 +02:00
fix(ms365): direkte OAuth2 URL-Konstruktion statt MSAL für Integration-Flow
MSAL-node v5 erzeugt bei getAuthCodeUrl mit reinen Graph-API-Scopes (ohne openid) einen fehlerhaften Authorize-URL → AADSTS900561. getIntegrationAuthUrl und handleIntegrationCallback verwenden jetzt direkte fetch-Aufrufe (analog zu refreshIntegrationToken) ohne MSAL, was den Fehler umgeht und denselben Standard-OAuth2-Flow garantiert. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
254d00c106
commit
1f6e59d362
1 changed files with 67 additions and 31 deletions
|
|
@ -332,65 +332,101 @@ export class EntraIdService implements OnModuleInit {
|
|||
|
||||
/**
|
||||
* Authorization-URL fuer MS365 Integration generieren.
|
||||
* Verwendet erweiterte Scopes (Mail, Calendar, Tasks).
|
||||
* Direkte URL-Konstruktion (kein MSAL) — umgeht MSAL v5 Kompatibilitaetsprobleme
|
||||
* mit reinen Graph-API-Scopes (ohne openid).
|
||||
* @param state CSRF-Token
|
||||
* @param redirectUri Optionaler Override (fuer dynamischen Host-basierten URI)
|
||||
*/
|
||||
async getIntegrationAuthUrl(state: string, redirectUri?: string): Promise<string> {
|
||||
if (!this.msalClient) {
|
||||
const ssoConfig = await this.loadConfigFromRedis();
|
||||
const tenantId =
|
||||
ssoConfig?.tenantId ?? this.config.get<string>('AZURE_TENANT_ID') ?? '';
|
||||
const clientId =
|
||||
ssoConfig?.clientId ?? this.config.get<string>('AZURE_CLIENT_ID') ?? '';
|
||||
|
||||
if (!tenantId || !clientId) {
|
||||
throw new ServiceUnavailableException(
|
||||
'Microsoft SSO ist nicht konfiguriert',
|
||||
);
|
||||
}
|
||||
|
||||
const authUrlRequest: AuthorizationUrlRequest = {
|
||||
scopes: this.integrationScopes,
|
||||
redirectUri: redirectUri || this.integrationRedirectUri,
|
||||
const params = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
response_type: 'code',
|
||||
redirect_uri: redirectUri || this.integrationRedirectUri,
|
||||
scope: this.integrationScopes.join(' '),
|
||||
state,
|
||||
prompt: 'consent', // Immer Zustimmung anfordern fuer neue Scopes
|
||||
};
|
||||
prompt: 'consent',
|
||||
response_mode: 'query',
|
||||
});
|
||||
|
||||
return this.msalClient.getAuthCodeUrl(authUrlRequest);
|
||||
const authUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize?${params.toString()}`;
|
||||
this.logger.log(`M365 Integration Auth URL generiert (tenant: ${tenantId})`);
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Code gegen M365-Tokens tauschen.
|
||||
* Gibt Access-Token, Refresh-Token und Ablaufzeit zurueck.
|
||||
* Authorization Code gegen M365-Tokens tauschen (Standard OAuth2 POST).
|
||||
* Direkte fetch-Implementierung analog zu refreshIntegrationToken —
|
||||
* umgeht MSAL v5 Probleme mit acquireTokenByCode fuer Graph-Scopes.
|
||||
* @param code Authorization Code von Microsoft
|
||||
* @param redirectUri Muss exakt mit dem URI aus dem Auth-Request uebereinstimmen
|
||||
*/
|
||||
async handleIntegrationCallback(code: string, redirectUri?: string): Promise<M365TokenResult> {
|
||||
if (!this.msalClient) {
|
||||
const ssoConfig = await this.loadConfigFromRedis();
|
||||
const tenantId =
|
||||
ssoConfig?.tenantId ?? this.config.get<string>('AZURE_TENANT_ID') ?? '';
|
||||
const clientId =
|
||||
ssoConfig?.clientId ?? this.config.get<string>('AZURE_CLIENT_ID') ?? '';
|
||||
const clientSecret =
|
||||
ssoConfig?.clientSecret ??
|
||||
this.config.get<string>('AZURE_CLIENT_SECRET') ??
|
||||
'';
|
||||
|
||||
if (!tenantId || !clientId || !clientSecret) {
|
||||
throw new ServiceUnavailableException(
|
||||
'Microsoft SSO ist nicht konfiguriert',
|
||||
'Azure-Konfiguration fehlt fuer Token-Exchange',
|
||||
);
|
||||
}
|
||||
|
||||
const tokenRequest: AuthorizationCodeRequest = {
|
||||
const params = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
code,
|
||||
scopes: this.integrationScopes,
|
||||
redirectUri: redirectUri || this.integrationRedirectUri,
|
||||
};
|
||||
redirect_uri: redirectUri || this.integrationRedirectUri,
|
||||
grant_type: 'authorization_code',
|
||||
scope: this.integrationScopes.join(' '),
|
||||
});
|
||||
|
||||
const response: AuthenticationResult =
|
||||
await this.msalClient.acquireTokenByCode(tokenRequest);
|
||||
const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
||||
const resp = await fetch(tokenEndpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params.toString(),
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const err = (await resp.json()) as {
|
||||
error_description?: string;
|
||||
error?: string;
|
||||
};
|
||||
throw new Error(
|
||||
`Token-Exchange fehlgeschlagen: ${err.error_description ?? err.error ?? resp.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = (await resp.json()) as {
|
||||
access_token: string;
|
||||
refresh_token?: string;
|
||||
expires_in: number;
|
||||
};
|
||||
|
||||
this.logger.log('M365 Integration Token erhalten');
|
||||
|
||||
// Refresh-Token aus MSAL-Antwort (erfordert offline_access Scope)
|
||||
// In msal-node >= 2.x ist refreshToken in der Antwort verfuegbar
|
||||
const rawResponse = response as AuthenticationResult & {
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
const refreshToken = rawResponse.refreshToken ?? '';
|
||||
const tenantId =
|
||||
(response.account?.tenantId) ?? '';
|
||||
|
||||
return {
|
||||
accessToken: response.accessToken,
|
||||
refreshToken,
|
||||
expiresAt: response.expiresOn ?? new Date(Date.now() + 3600 * 1000),
|
||||
accessToken: data.access_token,
|
||||
refreshToken: data.refresh_token ?? '',
|
||||
expiresAt: new Date(Date.now() + data.expires_in * 1000),
|
||||
scopes: this.integrationScopes,
|
||||
msTenantId: tenantId,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue