diff --git a/ee/branding/api/index.ts b/ee/branding/api/index.ts index 5d441758f..1858bd120 100644 --- a/ee/branding/api/index.ts +++ b/ee/branding/api/index.ts @@ -15,8 +15,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { res.setHeader('Allow', 'GET'); res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }); } - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; res.status(statusCode).json({ error: { message } }); } diff --git a/ee/identity-federation/api/metadata.ts b/ee/identity-federation/api/metadata.ts index 96234b129..6c40ea088 100644 --- a/ee/identity-federation/api/metadata.ts +++ b/ee/identity-federation/api/metadata.ts @@ -32,8 +32,8 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => { } res.status(200).send(metadata.xml); - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; return res.status(statusCode).json({ error: { message }, diff --git a/ee/product/api/[productId].ts b/ee/product/api/[productId].ts index a3006a0cf..4e7db4074 100644 --- a/ee/product/api/[productId].ts +++ b/ee/product/api/[productId].ts @@ -15,8 +15,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { res.setHeader('Allow', 'GET,DELETE'); res.status(405).json({ error: { message: `Method ${req.method} Not Allowed` } }); } - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; res.status(statusCode).json({ error: { message } }); } }; diff --git a/ee/product/api/index.ts b/ee/product/api/index.ts index 2e4dbafe8..1392fac2e 100644 --- a/ee/product/api/index.ts +++ b/ee/product/api/index.ts @@ -12,8 +12,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { res.setHeader('Allow', 'POST'); res.status(405).json({ error: { message: `Method ${req.method} Not Allowed` } }); } - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; res.status(statusCode).json({ error: { message } }); } }; diff --git a/internal-ui/src/identity-federation/EditIdentityFederationApp.tsx b/internal-ui/src/identity-federation/EditIdentityFederationApp.tsx index f08de33a8..c0496247e 100644 --- a/internal-ui/src/identity-federation/EditIdentityFederationApp.tsx +++ b/internal-ui/src/identity-federation/EditIdentityFederationApp.tsx @@ -55,8 +55,8 @@ export const EditIdentityFederationApp = ({ await fetch(urls.deleteApp, { method: 'DELETE', headers: defaultHeaders }); setDelModalVisible(false); onDelete?.(); - } catch (error: any) { - onError?.(error); + } catch (err: any) { + onError?.(err); } }; diff --git a/lib/api/default.ts b/lib/api/default.ts index eab91d073..ae8ab324e 100644 --- a/lib/api/default.ts +++ b/lib/api/default.ts @@ -21,9 +21,9 @@ export const defaultHandler = async (req: NextApiRequest, res: NextApiResponse, // Call the handler await handler(req, res); return; - } catch (error: any) { - const message = error.message || 'Internal Server Error'; - const status = error.statusCode || 500; + } catch (err: any) { + const message = err.message || 'Internal Server Error'; + const status = err.statusCode || 500; console.error(`${req.method} ${req.url} - ${status} - ${message}`); diff --git a/npm/src/controller/connection/saml.ts b/npm/src/controller/connection/saml.ts index 9cb92b676..aee61aac0 100644 --- a/npm/src/controller/connection/saml.ts +++ b/npm/src/controller/connection/saml.ts @@ -32,8 +32,8 @@ async function fetchMetadata(resource: string) { timeout: 8000, }); return response.data; - } catch (error: any) { - throw new JacksonError("Couldn't fetch XML data", error.response?.status || 400); + } catch (err: any) { + throw new JacksonError("Couldn't fetch XML data", err.response?.status || 400); } } diff --git a/npm/src/controller/oauth.ts b/npm/src/controller/oauth.ts index 87142fa39..bf6a47648 100644 --- a/npm/src/controller/oauth.ts +++ b/npm/src/controller/oauth.ts @@ -77,7 +77,9 @@ export class OAuthController implements IOAuthController { }); } - public async authorize(body: OAuthReq): Promise<{ redirect_url?: string; authorize_form?: string }> { + public async authorize( + body: OAuthReq + ): Promise<{ redirect_url?: string; authorize_form?: string; error?: string }> { const { tenant, product, @@ -104,6 +106,10 @@ export class OAuthController implements IOAuthController { let isOIDCFederated: boolean | undefined; let connection: SAMLSSORecord | OIDCSSORecord | undefined; let fedApp: IdentityFederationApp | undefined; + let connectionIsSAML; + let connectionIsOIDC; + let protocol; + const login_type = 'sp-initiated'; try { requestedTenant = tenant; @@ -183,6 +189,7 @@ export class OAuthController implements IOAuthController { // First we check if it's a federated connection if (client_id.startsWith(`${clientIDFederatedPrefix}${clientIDOIDCPrefix}`)) { isOIDCFederated = true; + protocol = 'OIDC Federation'; fedApp = await this.idFedApp.get({ id: client_id.replace(clientIDFederatedPrefix, ''), }); @@ -226,6 +233,10 @@ export class OAuthController implements IOAuthController { throw new JacksonError('IdP connection not found.', 403); } + connectionIsSAML = 'idpMetadata' in connection && connection.idpMetadata !== undefined; + connectionIsOIDC = 'oidcProvider' in connection && connection.oidcProvider !== undefined; + protocol = isOIDCFederated ? 'OIDC Federation' : connectionIsSAML ? 'SAML' : 'OIDC'; + if (!allowed.redirect(redirect_uri, connection.redirectUrl as string[])) { if (fedApp) { if (!allowed.redirect(redirect_uri, fedApp.redirectUrl as string[])) { @@ -241,6 +252,10 @@ export class OAuthController implements IOAuthController { } } catch (err: unknown) { const error_description = getErrorMessage(err); + metrics.increment('oauthAuthorizeError', { + protocol, + login_type, + }); // Save the error trace await this.ssoTraces.saveTrace({ error: error_description, @@ -262,9 +277,6 @@ export class OAuthController implements IOAuthController { const oAuthClientReqError = !state || response_type !== 'code'; - const connectionIsSAML = 'idpMetadata' in connection && connection.idpMetadata !== undefined; - const connectionIsOIDC = 'oidcProvider' in connection && connection.oidcProvider !== undefined; - if (isMissingJWTKeysForOIDCFlow || oAuthClientReqError || (!connectionIsSAML && !connectionIsOIDC)) { let error, error_description; if (isMissingJWTKeysForOIDCFlow) { @@ -288,6 +300,11 @@ export class OAuthController implements IOAuthController { error_description = 'Connection appears to be misconfigured'; } + metrics.increment('oauthAuthorizeError', { + protocol, + login_type, + }); + // Save the error trace const traceId = await this.ssoTraces.saveTrace({ error: error_description, @@ -307,6 +324,7 @@ export class OAuthController implements IOAuthController { redirect_uri, state, }), + error: `${error} - ${error_description}`, }; } @@ -333,6 +351,10 @@ export class OAuthController implements IOAuthController { } else { // This code here is kept for backward compatibility. We now have validation while adding the SSO connection to ensure binding is present. const error_description = 'SAML binding could not be retrieved'; + metrics.increment('oauthAuthorizeError', { + protocol, + login_type, + }); // Save the error trace const traceId = await this.ssoTraces.saveTrace({ error: error_description, @@ -370,6 +392,10 @@ export class OAuthController implements IOAuthController { }); } catch (err: unknown) { const error_description = getErrorMessage(err); + metrics.increment('oauthAuthorizeError', { + protocol, + login_type, + }); // Save the error trace const traceId = await this.ssoTraces.saveTrace({ error: error_description, @@ -445,6 +471,10 @@ export class OAuthController implements IOAuthController { }).href; } catch (err: unknown) { const error_description = getErrorMessage(err); + metrics.increment('oauthAuthorizeError', { + protocol, + login_type, + }); // Save the error trace const traceId = await this.ssoTraces.saveTrace({ error: error_description, @@ -472,7 +502,10 @@ export class OAuthController implements IOAuthController { } // Session persistence happens here try { - const requested = { client_id, state, redirect_uri } as Record; + const requested = { client_id, state, redirect_uri, protocol, login_type } as Record< + string, + string | boolean | string[] + >; if (requestedTenant) { requested.tenant = requestedTenant; } @@ -556,6 +589,10 @@ export class OAuthController implements IOAuthController { throw 'Connection appears to be misconfigured'; } catch (err: unknown) { const error_description = getErrorMessage(err); + metrics.increment('oauthAuthorizeError', { + protocol, + login_type, + }); // Save the error trace const traceId = await this.ssoTraces.saveTrace({ error: error_description, @@ -582,7 +619,7 @@ export class OAuthController implements IOAuthController { public async samlResponse( body: SAMLResponsePayload - ): Promise<{ redirect_url?: string; app_select_form?: string; response_form?: string }> { + ): Promise<{ redirect_url?: string; app_select_form?: string; response_form?: string; error?: string }> { let connection: SAMLSSORecord | undefined; let rawResponse: string | undefined; let sessionId: string | undefined; @@ -594,6 +631,7 @@ export class OAuthController implements IOAuthController { let validateOpts: ValidateOption; let redirect_uri: string | undefined; const { SAMLResponse, idp_hint, RelayState = '' } = body; + let protocol, login_type; try { isIdPFlow = !RelayState.startsWith(relayStatePrefix); @@ -608,6 +646,10 @@ export class OAuthController implements IOAuthController { ); } + login_type = isIdPFlow ? 'idp-initiated' : 'sp-initiated'; + if (isIdPFlow) { + protocol = 'SAML'; + } sessionId = RelayState.replace(relayStatePrefix, ''); if (!issuer) { @@ -634,7 +676,7 @@ export class OAuthController implements IOAuthController { isSAMLFederated = session && 'samlFederated' in session; isOIDCFederated = session && 'oidcFederated' in session; const isSPFlow = !isIdPFlow && !isSAMLFederated; - + protocol = isOIDCFederated ? 'OIDC Federation' : isSAMLFederated ? 'SAML Federation' : 'SAML'; // IdP initiated SSO flow if (isIdPFlow) { const response = await this.ssoHandler.resolveConnection({ @@ -711,6 +753,7 @@ export class OAuthController implements IOAuthController { redirect_uri = ((session && session.redirect_uri) as string) || connection.defaultRedirectUrl; } catch (err: unknown) { + metrics.increment('oAuthResponseError', { protocol, login_type }); // Save the error trace await this.ssoTraces.saveTrace({ error: getErrorMessage(err), @@ -761,6 +804,7 @@ export class OAuthController implements IOAuthController { return { redirect_url: redirect.success(redirect_uri, params) }; } catch (err: unknown) { + metrics.increment('oAuthResponseError', { protocol, login_type }); const error_description = getErrorMessage(err); // Trace the error const traceId = await this.ssoTraces.saveTrace({ @@ -795,19 +839,22 @@ export class OAuthController implements IOAuthController { redirect_uri, state: session?.requested?.state, }), + error: `access_denied - ${error_description}`, }; } } public async oidcAuthzResponse( body: OIDCAuthzResponsePayload - ): Promise<{ redirect_url?: string; response_form?: string }> { + ): Promise<{ redirect_url?: string; response_form?: string; error?: string }> { let oidcConnection: OIDCSSORecord | undefined; let session: any; let isSAMLFederated: boolean | undefined; let isOIDCFederated: boolean | undefined; let redirect_uri: string | undefined; let profile; + let protocol; + const login_type = 'sp-initiated'; const callbackParams = body; @@ -826,6 +873,8 @@ export class OAuthController implements IOAuthController { isSAMLFederated = session && 'samlFederated' in session; isOIDCFederated = session && 'oidcFederated' in session; + protocol = isOIDCFederated ? 'OIDC Federation' : isSAMLFederated ? 'SAML Federation' : 'OIDC'; + oidcConnection = await this.connectionStore.get(session.id); if (!oidcConnection) { @@ -849,6 +898,7 @@ export class OAuthController implements IOAuthController { } } } catch (err) { + metrics.increment('oAuthResponseError', { protocol, login_type }); await this.ssoTraces.saveTrace({ error: getErrorMessage(err), context: { @@ -934,6 +984,7 @@ export class OAuthController implements IOAuthController { } catch (err: any) { const { error, error_description, error_uri, session_state, scope, stack } = err; const error_message = error_description || getErrorMessage(err); + metrics.increment('oAuthResponseError', { protocol, login_type }); const traceId = await this.ssoTraces.saveTrace({ error: error_message, context: { @@ -969,6 +1020,7 @@ export class OAuthController implements IOAuthController { redirect_uri: redirect_uri!, state: session.state, }), + error: `${error} - ${error_message}`, }; } } @@ -1068,6 +1120,7 @@ export class OAuthController implements IOAuthController { public async token(body: OAuthTokenReq, authHeader?: string | null): Promise { let basic_client_id: string | undefined; let basic_client_secret: string | undefined; + let protocol, login_type; try { if (authHeader) { // Authorization: Basic {Base64(:)} @@ -1087,137 +1140,147 @@ export class OAuthController implements IOAuthController { metrics.increment('oauthToken'); - if (grant_type !== 'authorization_code') { - throw new JacksonError('Unsupported grant_type', 400); - } - - if (!code) { - throw new JacksonError('Please specify code', 400); - } - - const codeVal = await this.codeStore.get(code); - if (!codeVal || !codeVal.profile) { - throw new JacksonError('Invalid code', 403); - } - - if (codeVal.requested?.redirect_uri) { - if (redirect_uri !== codeVal.requested.redirect_uri) { - throw new JacksonError( - `Invalid request: ${!redirect_uri ? 'redirect_uri missing' : 'redirect_uri mismatch'}`, - 400 - ); + try { + if (grant_type !== 'authorization_code') { + throw new JacksonError('Unsupported grant_type', 400); } - } - if (code_verifier) { - // PKCE flow - let cv = code_verifier; - if (codeVal.session.code_challenge_method?.toLowerCase() === 's256') { - cv = codeVerifier.encode(code_verifier); + if (!code) { + throw new JacksonError('Please specify code', 400); } - if (codeVal.session.code_challenge !== cv) { - throw new JacksonError('Invalid code_verifier', 401); + const codeVal = await this.codeStore.get(code); + if (!codeVal || !codeVal.profile) { + throw new JacksonError('Invalid code', 403); } - // For Federation flow, we need to verify the client_secret - if (client_id?.startsWith(`${clientIDFederatedPrefix}${clientIDOIDCPrefix}`)) { - if ( - client_id !== codeVal.session?.oidcFederated?.clientID || - client_secret !== codeVal.session?.oidcFederated?.clientSecret - ) { - throw new JacksonError('Invalid client_id or client_secret', 401); + protocol = codeVal.requested.protocol || 'SAML'; + login_type = codeVal.isIdPFlow ? 'idp-initiated' : 'sp-initiated'; + + if (codeVal.requested?.redirect_uri) { + if (redirect_uri !== codeVal.requested.redirect_uri) { + throw new JacksonError( + `Invalid request: ${!redirect_uri ? 'redirect_uri missing' : 'redirect_uri mismatch'}`, + 400 + ); } } - } else if (client_id && client_secret) { - // check if we have an encoded client_id - if (client_id !== 'dummy') { - const sp = getEncodedTenantProduct(client_id); - if (!sp) { - // OAuth flow - if (client_id !== codeVal.clientID || client_secret !== codeVal.clientSecret) { - throw new JacksonError('Invalid client_id or client_secret', 401); - } - } else { + + if (code_verifier) { + // PKCE flow + let cv = code_verifier; + if (codeVal.session.code_challenge_method?.toLowerCase() === 's256') { + cv = codeVerifier.encode(code_verifier); + } + + if (codeVal.session.code_challenge !== cv) { + throw new JacksonError('Invalid code_verifier', 401); + } + + // For Federation flow, we need to verify the client_secret + if (client_id?.startsWith(`${clientIDFederatedPrefix}${clientIDOIDCPrefix}`)) { if ( - !codeVal.isIdPFlow && - (sp.tenant !== codeVal.requested?.tenant || sp.product !== codeVal.requested?.product) + client_id !== codeVal.session?.oidcFederated?.clientID || + client_secret !== codeVal.session?.oidcFederated?.clientSecret ) { - throw new JacksonError('Invalid tenant or product', 401); + throw new JacksonError('Invalid client_id or client_secret', 401); + } + } + } else if (client_id && client_secret) { + // check if we have an encoded client_id + if (client_id !== 'dummy') { + const sp = getEncodedTenantProduct(client_id); + if (!sp) { + // OAuth flow + if (client_id !== codeVal.clientID || client_secret !== codeVal.clientSecret) { + throw new JacksonError('Invalid client_id or client_secret', 401); + } + } else { + if ( + !codeVal.isIdPFlow && + (sp.tenant !== codeVal.requested?.tenant || sp.product !== codeVal.requested?.product) + ) { + throw new JacksonError('Invalid tenant or product', 401); + } + // encoded client_id, verify client_secret + if (client_secret !== this.opts.clientSecretVerifier) { + throw new JacksonError('Invalid client_secret', 401); + } } - // encoded client_id, verify client_secret - if (client_secret !== this.opts.clientSecretVerifier) { + } else { + if (client_secret !== this.opts.clientSecretVerifier && client_secret !== codeVal.clientSecret) { throw new JacksonError('Invalid client_secret', 401); } } - } else { - if (client_secret !== this.opts.clientSecretVerifier && client_secret !== codeVal.clientSecret) { - throw new JacksonError('Invalid client_secret', 401); - } + } else if (codeVal && codeVal.session) { + throw new JacksonError('Please specify client_secret or code_verifier', 401); } - } else if (codeVal && codeVal.session) { - throw new JacksonError('Please specify client_secret or code_verifier', 401); - } - // store details against a token - const token = crypto.randomBytes(20).toString('hex'); + // store details against a token + const token = crypto.randomBytes(20).toString('hex'); - const tokenVal = { - ...codeVal.profile, - requested: codeVal.requested, - }; - const requestedOIDCFlow = !!codeVal.requested?.oidc; - const requestHasNonce = !!codeVal.requested?.nonce; - if (requestedOIDCFlow) { - const { jwtSigningKeys, jwsAlg } = this.opts.openid ?? {}; - if (!jwtSigningKeys || !isJWSKeyPairLoaded(jwtSigningKeys)) { - throw new JacksonError('JWT signing keys are not loaded', 500); - } - let claims: Record = requestHasNonce ? { nonce: codeVal.requested.nonce } : {}; - claims = { - ...claims, - id: codeVal.profile.claims.id, - email: codeVal.profile.claims.email, - firstName: codeVal.profile.claims.firstName, - lastName: codeVal.profile.claims.lastName, - roles: codeVal.profile.claims.roles, - groups: codeVal.profile.claims.groups, + const tokenVal = { + ...codeVal.profile, + requested: codeVal.requested, + login_type, + protocol, }; - const signingKey = await loadJWSPrivateKey(jwtSigningKeys.private, jwsAlg!); - const kid = await computeKid(jwtSigningKeys.public, jwsAlg!); - const id_token = await new jose.SignJWT(claims) - .setProtectedHeader({ alg: jwsAlg!, kid }) - .setIssuedAt() - .setIssuer(this.opts.externalUrl) - .setSubject(codeVal.profile.claims.id) - .setAudience(tokenVal.requested.client_id) - .setExpirationTime(`${this.opts.db.ttl}s`) // identity token only really needs to be valid long enough for it to be verified by the client application. - .sign(signingKey); - tokenVal.id_token = id_token; - tokenVal.claims.sub = codeVal.profile.claims.id; - } + const requestedOIDCFlow = !!codeVal.requested?.oidc; + const requestHasNonce = !!codeVal.requested?.nonce; + if (requestedOIDCFlow) { + const { jwtSigningKeys, jwsAlg } = this.opts.openid ?? {}; + if (!jwtSigningKeys || !isJWSKeyPairLoaded(jwtSigningKeys)) { + throw new JacksonError('JWT signing keys are not loaded', 500); + } + let claims: Record = requestHasNonce ? { nonce: codeVal.requested.nonce } : {}; + claims = { + ...claims, + id: codeVal.profile.claims.id, + email: codeVal.profile.claims.email, + firstName: codeVal.profile.claims.firstName, + lastName: codeVal.profile.claims.lastName, + roles: codeVal.profile.claims.roles, + groups: codeVal.profile.claims.groups, + }; + const signingKey = await loadJWSPrivateKey(jwtSigningKeys.private, jwsAlg!); + const kid = await computeKid(jwtSigningKeys.public, jwsAlg!); + const id_token = await new jose.SignJWT(claims) + .setProtectedHeader({ alg: jwsAlg!, kid }) + .setIssuedAt() + .setIssuer(this.opts.externalUrl) + .setSubject(codeVal.profile.claims.id) + .setAudience(tokenVal.requested.client_id) + .setExpirationTime(`${this.opts.db.ttl}s`) // identity token only really needs to be valid long enough for it to be verified by the client application. + .sign(signingKey); + tokenVal.id_token = id_token; + tokenVal.claims.sub = codeVal.profile.claims.id; + } - await this.tokenStore.put(token, tokenVal); + await this.tokenStore.put(token, tokenVal); - // delete the code - try { - await this.codeStore.delete(code); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (_err) { - // ignore error - } + // delete the code + try { + await this.codeStore.delete(code); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_err) { + // ignore error + } - const tokenResponse: OAuthTokenRes = { - access_token: token, - token_type: 'bearer', - expires_in: this.opts.db.ttl!, - }; + const tokenResponse: OAuthTokenRes = { + access_token: token, + token_type: 'bearer', + expires_in: this.opts.db.ttl!, + }; - if (requestedOIDCFlow) { - tokenResponse.id_token = tokenVal.id_token; - } + if (requestedOIDCFlow) { + tokenResponse.id_token = tokenVal.id_token; + } - return tokenResponse; + return tokenResponse; + } catch (err) { + metrics.increment('oauthTokenError', { protocol, login_type }); + throw err; + } } /** @@ -1273,6 +1336,7 @@ export class OAuthController implements IOAuthController { metrics.increment('oauthUserInfo'); if (!rsp || !rsp.claims) { + metrics.increment('oauthUserInfoError', { protocol: rsp.protocol, login_type: rsp.login_type }); throw new JacksonError('Invalid token', 403); } diff --git a/npm/src/db/dynamoDb.ts b/npm/src/db/dynamoDb.ts index 685efe77a..fe207cfc7 100644 --- a/npm/src/db/dynamoDb.ts +++ b/npm/src/db/dynamoDb.ts @@ -73,12 +73,12 @@ class DynamoDB implements DatabaseDriver { }, }) ); - } catch (error: any) { + } catch (err: any) { if ( - !error?.message?.includes('Cannot create preexisting table') && - !error?.message?.toLowerCase().includes('table already exists') + !err?.message?.includes('Cannot create preexisting table') && + !err?.message?.toLowerCase().includes('table already exists') ) { - throw error; + throw err; } } try { @@ -145,12 +145,12 @@ class DynamoDB implements DatabaseDriver { TableName: indexTableName, }) ); - } catch (error: any) { + } catch (err: any) { if ( - !error?.message?.includes('Cannot create preexisting table') && - !error?.message?.toLowerCase().includes('table already exists') + !err?.message?.includes('Cannot create preexisting table') && + !err?.message?.toLowerCase().includes('table already exists') ) { - throw error; + throw err; } } return this; diff --git a/npm/src/directory-sync/batch-events/queue.ts b/npm/src/directory-sync/batch-events/queue.ts index ca010d6c9..ec4c7604f 100644 --- a/npm/src/directory-sync/batch-events/queue.ts +++ b/npm/src/directory-sync/batch-events/queue.ts @@ -153,11 +153,11 @@ export class EventProcessor { } await this.logWebhookEvent(directory, events, status); - } catch (error: any) { - const message = `Error sending payload to webhook ${directory.webhook.endpoint}. Marking the events as failed. ${error.message}`; - const status = error.response?.status || 500; + } catch (err: any) { + const message = `Error sending payload to webhook ${directory.webhook.endpoint}. Marking the events as failed. ${err.message}`; + const status = err.response?.status || 500; - console.error(message, error); + console.error(message, err); await this.markAsFailed(events); await this.logWebhookEvent(directory, events, status); diff --git a/npm/src/directory-sync/non-scim/google/oauth.ts b/npm/src/directory-sync/non-scim/google/oauth.ts index 6a487277f..2cc4718ab 100644 --- a/npm/src/directory-sync/non-scim/google/oauth.ts +++ b/npm/src/directory-sync/non-scim/google/oauth.ts @@ -71,8 +71,8 @@ export class GoogleAuth { }; return { data, error: null }; - } catch (error: any) { - return apiError(error); + } catch (err: any) { + return apiError(err); } } @@ -92,8 +92,8 @@ export class GoogleAuth { const { tokens } = await oauth2Client.getToken(code); return { data: tokens, error: null }; - } catch (error: any) { - return apiError(error); + } catch (err: any) { + return apiError(err); } } @@ -124,8 +124,8 @@ export class GoogleAuth { } return { data, error: null }; - } catch (error: any) { - return apiError(error); + } catch (err: any) { + return apiError(err); } } } diff --git a/npm/src/opentelemetry/metrics.ts b/npm/src/opentelemetry/metrics.ts index ae4002f0c..bec27c9df 100644 --- a/npm/src/opentelemetry/metrics.ts +++ b/npm/src/opentelemetry/metrics.ts @@ -1,4 +1,4 @@ -import { incrementCounter } from '@boxyhq/metrics'; +import { incrementCounter, type CounterOperationParams } from '@boxyhq/metrics'; const METER = 'jackson'; @@ -27,19 +27,46 @@ const counters = { name: 'jackson.oauth.authorize', counterOptions: { description: 'Number of oauth authorize requests' }, }), + oauthAuthorizeError: (counterAttributes: CounterOperationParams['counterAttributes']) => + incrementCounter({ + meter: METER, + name: 'jackson.oauth.authorize.error', + counterOptions: { description: 'Number of errors in oauth authorize requests' }, + counterAttributes, + }), + oAuthResponseError: (counterAttributes: CounterOperationParams['counterAttributes']) => + incrementCounter({ + meter: METER, + name: 'jackson.oauth.response.error', + counterOptions: { description: 'Number of errors in idp response path' }, + counterAttributes, + }), oauthToken: () => incrementCounter({ meter: METER, name: 'jackson.oauth.token', counterOptions: { description: 'Number of oauth token requests' }, }), - + oauthTokenError: (counterAttributes: CounterOperationParams['counterAttributes']) => + incrementCounter({ + meter: METER, + name: 'jackson.oauth.token.error', + counterOptions: { description: 'Number of errors in oauth token requests' }, + counterAttributes, + }), oauthUserInfo: () => incrementCounter({ meter: METER, name: 'jackson.oauth.userinfo', counterOptions: { description: 'Number of oauth user info requests' }, }), + oauthUserInfoError: (counterAttributes: CounterOperationParams['counterAttributes']) => + incrementCounter({ + meter: METER, + name: 'jackson.oauth.userinfo.error', + counterOptions: { description: 'Number of errors in oauth user info requests' }, + counterAttributes, + }), createDsyncConnection: () => incrementCounter({ @@ -71,10 +98,13 @@ const counters = { }, }; -const increment = (action: keyof typeof counters) => { +const increment = ( + action: keyof typeof counters, + counterAttributes?: CounterOperationParams['counterAttributes'] +) => { const counterIncrement = counters[action]; if (typeof counterIncrement === 'function') { - counterIncrement(); + counterIncrement(counterAttributes); } }; diff --git a/npm/src/typings.ts b/npm/src/typings.ts index f38269ade..b9731814b 100644 --- a/npm/src/typings.ts +++ b/npm/src/typings.ts @@ -196,13 +196,13 @@ export interface IConnectionAPIController { } export interface IOAuthController { - authorize(body: OAuthReq): Promise<{ redirect_url?: string; authorize_form?: string }>; + authorize(body: OAuthReq): Promise<{ redirect_url?: string; authorize_form?: string; error?: string }>; samlResponse( body: SAMLResponsePayload - ): Promise<{ redirect_url?: string; app_select_form?: string; response_form?: string }>; + ): Promise<{ redirect_url?: string; app_select_form?: string; response_form?: string; error?: string }>; oidcAuthzResponse( body: OIDCAuthzResponsePayload - ): Promise<{ redirect_url?: string; response_form?: string }>; + ): Promise<{ redirect_url?: string; response_form?: string; error?: string }>; token(body: OAuthTokenReq): Promise; userInfo(token: string): Promise; } diff --git a/pages/api/oauth/authorize.ts b/pages/api/oauth/authorize.ts index acc639538..126961c5f 100644 --- a/pages/api/oauth/authorize.ts +++ b/pages/api/oauth/authorize.ts @@ -12,17 +12,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const { oauthController } = await jackson(); const requestParams = req.method === 'GET' ? req.query : req.body; - const { redirect_url, authorize_form } = await oauthController.authorize( + const { redirect_url, authorize_form, error } = await oauthController.authorize( requestParams as unknown as OAuthReq ); if (redirect_url) { + if (error) { + console.error(`authorize error: ${error}`); + } res.redirect(302, redirect_url); } else { res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.send(authorize_form); } } catch (err: any) { - console.error('authorize error:', err); + console.error('authorize error: ', err); const { message, statusCode = 500 } = err; // set error in cookie redirect to error page setErrorCookie(res, { message, statusCode }, { path: '/error' }); diff --git a/pages/api/oauth/oidc.ts b/pages/api/oauth/oidc.ts index 9bd79dff0..18507dee5 100644 --- a/pages/api/oauth/oidc.ts +++ b/pages/api/oauth/oidc.ts @@ -12,11 +12,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const { oauthController } = await jackson(); - const { redirect_url, response_form } = await oauthController.oidcAuthzResponse( + const { redirect_url, response_form, error } = await oauthController.oidcAuthzResponse( req.query as OIDCAuthzResponsePayload ); if (redirect_url) { + if (error) { + console.error(`Error processing OIDC IdP response: ${error}`); + } res.redirect(302, redirect_url); } @@ -25,8 +28,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) res.send(response_form); } } catch (err: any) { - console.error('callback error:', err); const { message, statusCode = 500 } = err; + console.error('Error processing OIDC IdP response:', err); // set error in cookie redirect to error page setErrorCookie(res, { message, statusCode }, { path: '/error' }); res.redirect(302, '/error'); diff --git a/pages/api/oauth/saml.ts b/pages/api/oauth/saml.ts index 0e7ba7eaf..2e9ac79e3 100644 --- a/pages/api/oauth/saml.ts +++ b/pages/api/oauth/saml.ts @@ -20,13 +20,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }; // Handle SAML Response generated by IdP - const { redirect_url, app_select_form, response_form } = await oauthController.samlResponse({ + const { redirect_url, app_select_form, response_form, error } = await oauthController.samlResponse({ SAMLResponse, RelayState, idp_hint, }); if (redirect_url) { + if (error) { + console.error(`Error processing SAML IdP response: ${error}`); + } res.redirect(302, redirect_url); return; } @@ -42,8 +45,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) res.send(response_form); return; } - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; + console.error('Error processing SAML IdP response:', err); setErrorCookie(res, { message, statusCode }, { path: '/error' }); diff --git a/pages/api/scim/oauth/authorize.ts b/pages/api/scim/oauth/authorize.ts index 3798393e9..418bf5f70 100644 --- a/pages/api/scim/oauth/authorize.ts +++ b/pages/api/scim/oauth/authorize.ts @@ -27,8 +27,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { res.redirect(302, data.authorizationUrl).end(); return; - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; return res.status(statusCode).json({ error: { message } }); } diff --git a/pages/api/scim/oauth/callback.ts b/pages/api/scim/oauth/callback.ts index 30a646577..b715b1566 100644 --- a/pages/api/scim/oauth/callback.ts +++ b/pages/api/scim/oauth/callback.ts @@ -45,8 +45,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } return res.send('Authorized done successfully. You may close this window.'); - } catch (error: any) { - return res.status(500).send({ error }); + } catch (err: any) { + return res.status(500).send({ err }); } }; diff --git a/pages/api/setup/[token]/directory-sync/[directoryId]/index.ts b/pages/api/setup/[token]/directory-sync/[directoryId]/index.ts index 2a816eef9..7c858cf3e 100644 --- a/pages/api/setup/[token]/directory-sync/[directoryId]/index.ts +++ b/pages/api/setup/[token]/directory-sync/[directoryId]/index.ts @@ -21,8 +21,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { res.setHeader('Allow', 'PATCH, GET, DELETE'); res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }); } - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; return res.status(statusCode).json({ error: { message } }); } diff --git a/pages/api/setup/[token]/directory-sync/index.ts b/pages/api/setup/[token]/directory-sync/index.ts index 9b1e29dd5..a322f364f 100644 --- a/pages/api/setup/[token]/directory-sync/index.ts +++ b/pages/api/setup/[token]/directory-sync/index.ts @@ -21,8 +21,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { res.setHeader('Allow', 'PUT'); res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }); } - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; return res.status(statusCode).json({ error: { message } }); } diff --git a/pages/api/setup/[token]/index.ts b/pages/api/setup/[token]/index.ts index 6e7fe674b..e94b14747 100644 --- a/pages/api/setup/[token]/index.ts +++ b/pages/api/setup/[token]/index.ts @@ -14,8 +14,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { res.setHeader('Allow', 'GET'); res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }); } - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; return res.status(statusCode).json({ error: { message } }); } diff --git a/pages/api/setup/[token]/sso-connection/[id].ts b/pages/api/setup/[token]/sso-connection/[id].ts index 17a63c5eb..ba5c8af19 100644 --- a/pages/api/setup/[token]/sso-connection/[id].ts +++ b/pages/api/setup/[token]/sso-connection/[id].ts @@ -17,8 +17,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { res.setHeader('Allow', 'GET'); res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }); } - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; return res.status(statusCode).json({ error: { message } }); } diff --git a/pages/api/setup/[token]/sso-connection/idp-entityid.ts b/pages/api/setup/[token]/sso-connection/idp-entityid.ts index 29170a485..8e682982b 100644 --- a/pages/api/setup/[token]/sso-connection/idp-entityid.ts +++ b/pages/api/setup/[token]/sso-connection/idp-entityid.ts @@ -18,8 +18,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { res.setHeader('Allow', 'GET'); res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }); } - } catch (error: any) { - const { message, statusCode = 500 } = error; + } catch (err: any) { + const { message, statusCode = 500 } = err; return res.status(statusCode).json({ error: { message } }); } diff --git a/pages/setup/[token]/sso-connection/new.tsx b/pages/setup/[token]/sso-connection/new.tsx index 872e1f1a6..99632efeb 100644 --- a/pages/setup/[token]/sso-connection/new.tsx +++ b/pages/setup/[token]/sso-connection/new.tsx @@ -164,7 +164,7 @@ export async function getServerSideProps({ locale, query }: GetServerSidePropsCo const source = fs.readFileSync(`${mdxDirectory}/${step}.mdx`, 'utf8'); mdxSource = await serialize(source, { mdxOptions: { remarkPlugins: [remarkGfm] } }); // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error: any) { + } catch (err: any) { return { redirect: { destination: `/setup/${token}/sso-connection/new`,