From bbda7485f6439938d5f5933f5a07fd09a922be18 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 3 Jul 2025 10:24:53 +0200 Subject: [PATCH] feat: adding client register Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- .../src/service/OidcRouter.test.ts | 2 ++ .../auth-backend/src/service/OidcRouter.ts | 28 ++++++++++++++++++- .../auth-backend/src/service/OidcService.ts | 28 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/service/OidcRouter.test.ts b/plugins/auth-backend/src/service/OidcRouter.test.ts index dbc6c88f93..8e53abbcaf 100644 --- a/plugins/auth-backend/src/service/OidcRouter.test.ts +++ b/plugins/auth-backend/src/service/OidcRouter.test.ts @@ -37,6 +37,8 @@ describe('OidcRouter', () => { }), } as unknown as UserInfoDatabase; + const mockOidc = {}; + const { server } = await startTestBackend({ features: [ createBackendPlugin({ diff --git a/plugins/auth-backend/src/service/OidcRouter.ts b/plugins/auth-backend/src/service/OidcRouter.ts index 6ada071c44..219e026a53 100644 --- a/plugins/auth-backend/src/service/OidcRouter.ts +++ b/plugins/auth-backend/src/service/OidcRouter.ts @@ -15,10 +15,12 @@ */ import Router from 'express-promise-router'; import { OidcService } from './OidcService'; -import { AuthenticationError } from '@backstage/errors'; +import { AuthenticationError, isError } from '@backstage/errors'; import { AuthService } from '@backstage/backend-plugin-api'; import { TokenIssuer } from '../identity/types'; import { UserInfoDatabase } from '../database/UserInfoDatabase'; +import { rest } from 'lodash'; +import { OidcDatabase } from '../database/OidcDatabase'; export class OidcRouter { private constructor(private readonly oidc: OidcService) {} @@ -28,6 +30,7 @@ export class OidcRouter { tokenIssuer: TokenIssuer; baseUrl: string; userInfo: UserInfoDatabase; + oidc: OidcDatabase; }) { return new OidcRouter(OidcService.create(options)); } @@ -68,6 +71,29 @@ export class OidcRouter { res.json(userInfo); }); + router.get('/v1/register', async (req, res) => { + // todo(blam): maybe add zod types for validating input + const registrationRequest = req.body; + if (!registrationRequest.redirect_uris?.length) { + res.status(400).json({ + error: 'invalid_request', + error_description: 'redirect_uris is required', + }); + return; + } + + try { + res.json(await this.oidc.registerClient(registrationRequest)); + } catch (e) { + res.status(500).json({ + error: 'server_error', + error_description: `Failed to register client: ${ + isError(e) ? e.message : 'Unknown error' + }`, + }); + } + }); + return router; } } diff --git a/plugins/auth-backend/src/service/OidcService.ts b/plugins/auth-backend/src/service/OidcService.ts index 5024b2cc8c..919c0dac64 100644 --- a/plugins/auth-backend/src/service/OidcService.ts +++ b/plugins/auth-backend/src/service/OidcService.ts @@ -18,6 +18,8 @@ import { TokenIssuer } from '../identity/types'; import { UserInfoDatabase } from '../database/UserInfoDatabase'; import { InputError } from '@backstage/errors'; import { decodeJwt } from 'jose'; +import crypto from 'crypto'; +import { OidcDatabase } from '../database/OidcDatabase'; export class OidcService { private constructor( @@ -25,6 +27,7 @@ export class OidcService { private readonly tokenIssuer: TokenIssuer, private readonly baseUrl: string, private readonly userInfo: UserInfoDatabase, + private readonly oidc: OidcDatabase, ) {} static create(options: { @@ -32,12 +35,14 @@ export class OidcService { tokenIssuer: TokenIssuer; baseUrl: string; userInfo: UserInfoDatabase; + oidc: OidcDatabase; }) { return new OidcService( options.auth, options.tokenIssuer, options.baseUrl, options.userInfo, + options.oidc, ); } @@ -65,6 +70,8 @@ export class OidcService { token_endpoint_auth_methods_supported: [], claims_supported: ['sub', 'ent'], grant_types_supported: [], + authorization_endpoint: `${this.baseUrl}/v1/authorize`, + registration_endpoint: `${this.baseUrl}/v1/register`, }; } @@ -89,4 +96,25 @@ export class OidcService { } return await this.userInfo.getUserInfo(userEntityRef); } + + public async registerClient(opts: { + responseTypes?: string[]; + grantTypes?: string[]; + clientName: string; + redirectUris?: string[]; + scope?: string; + }) { + const generatedClientId = crypto.randomUUID(); + const generatedClientSecret = crypto.randomUUID(); + + return await this.oidc.createClient({ + clientId: generatedClientId, + clientName: opts.clientName, + clientSecret: generatedClientSecret, + redirectUris: opts.redirectUris ?? [], + responseTypes: opts.responseTypes ?? ['code'], + grantTypes: opts.grantTypes ?? ['authorization_code'], + scope: opts.scope, + }); + } }