feat: adding client register

Signed-off-by: benjdlambert <ben@blam.sh>

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2025-07-03 10:24:53 +02:00
parent ac54ac21d3
commit bbda7485f6
3 changed files with 57 additions and 1 deletions
@@ -37,6 +37,8 @@ describe('OidcRouter', () => {
}),
} as unknown as UserInfoDatabase;
const mockOidc = {};
const { server } = await startTestBackend({
features: [
createBackendPlugin({
+27 -1
View File
@@ -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;
}
}
@@ -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,
});
}
}