UNPKG

@redocly/portal-plugin-gravitee-sso

Version:

Gravitee SSO plugin for @redocly/portal

127 lines (97 loc) 4.19 kB
import { setCookie } from 'hono/cookie'; import { DEFAULT_COOKIE_EXPIRATION } from '@redocly/realm/dist/shared/constants.js'; import { JWT_SECRET_KEY } from '@redocly/realm/dist/server/constants.js'; import * as jwt from '@redocly/realm/dist/server/web-server/jwt/jwt.js'; import type { Store } from '@redocly/realm/dist/server/store.js'; import type { Logger } from '@redocly/realm/dist/server/utils/reporter/logger.js'; import type { Context } from 'hono'; import type { GraviteeSsoConfig } from './config.js'; import { getPathPrefix, withPathPrefix } from '@redocly/theme/core/utils'; import { isGraviteeSsoConfig } from './is-gravitee-sso-config.js'; import { GraviteeSsoError, LOGIN_PAGE_SLUG } from './constants.js'; export default async function (ctx: Context, store: Store): Promise<Response> { const logger = ctx.get('logger') as Logger; const parsedUrl = new URL(ctx.req.url); const idpId = parsedUrl.searchParams.get('idpId'); const redirectTo = parsedUrl.searchParams.get('redirectTo') || '/'; const loginPagePathname = parsedUrl.searchParams.get('loginPagePathname') || withPathPrefix(LOGIN_PAGE_SLUG); if (!idpId) { logger.error('Gravitee SSO: idpId query parameter not found'); return ctx.text('Forbidden', 403); } const createErrorResponse = (error: GraviteeSsoError) => { const errorSearchParams = new URLSearchParams({ error, idpId, redirectTo, }); const errorRedirectUrl = `${loginPagePathname}?${errorSearchParams}`; return ctx.newResponse(null, 302, { Location: errorRedirectUrl }); }; const idpConfig = store.getConfig().ssoDirect?.[idpId] as GraviteeSsoConfig | unknown; if (!isGraviteeSsoConfig(idpConfig)) { logger.error(`Gravitee SSO: invalid idpId in query params: ${idpId}`); return ctx.text('Forbidden', 403); } const formData = await ctx.req.formData(); const username = formData.get('username'); const password = formData.get('password'); if (!username || !password) { return createErrorResponse(GraviteeSsoError.INVALID_CREDENTIALS); } const basicAuthToken = btoa(username + ':' + password); const apiUrl = `${idpConfig.apiBaseUrl}/portal/environments/${idpConfig.env || 'DEFAULT'}`; try { const authRes = await fetch(`${apiUrl}/auth/login`, { method: 'POST', headers: { Authorization: `Basic ${basicAuthToken}`, }, }); if (authRes.status === 401) { logger.verbose('Gravitee SSO callback: invalid username or password'); return createErrorResponse(GraviteeSsoError.INVALID_CREDENTIALS); } else if (authRes.status !== 200) { logger.error('Gravitee SSO callback: failed to fetch token from Gravitee API'); return createErrorResponse(GraviteeSsoError.UNKNOWN_ERROR); } const userRes = await fetch(`${apiUrl}/user`, { method: 'GET', headers: { Authorization: `Basic ${basicAuthToken}`, }, }); if (userRes.status !== 200) { logger.error('Gravitee SSO callback: failed to fetch user information from Gravitee API'); return createErrorResponse(GraviteeSsoError.UNKNOWN_ERROR); } const auth = await authRes.json(); const user = await userRes.json(); const userClaims = { ...user, sub: user.id, name: user.display_name || user.email || 'Unknown', idpId, }; const encodedToken = await jwt.sign(userClaims, JWT_SECRET_KEY); const expirationDate = Date.now() + DEFAULT_COOKIE_EXPIRATION * 1000; setCookie(ctx, 'idp_access_token', auth.token || '', { path: getPathPrefix() || '/', httpOnly: true, expires: new Date(expirationDate), }); const c1 = (ctx as any)._headers['set-cookie']; setCookie(ctx, 'authorization', encodedToken, { path: getPathPrefix() || '/', httpOnly: true, expires: new Date(expirationDate), }); const resp = ctx.newResponse(null, 302, { Location: withPathPrefix(redirectTo) }); resp.headers.append('set-cookie', c1); return resp; } catch (error) { logger.error(`Gravitee sso callback: ${error.message}`); return createErrorResponse(GraviteeSsoError.UNKNOWN_ERROR); } }