src/registrar/registrar-auth.service.ts
Handles OAuth2 token acquisition/caching and low-level registrar client creation. Also provides getRelyingPartyId which is shared by both access-certificate and registration-certificate flows.
Properties |
|
Methods |
|
constructor(configRepository: Repository<RegistrarConfigEntity>)
|
||||||
|
Defined in src/registrar/registrar-auth.service.ts:34
|
||||||
|
Parameters :
|
| Async getAccessToken | ||||||
getAccessToken(tenantId: string)
|
||||||
|
Defined in src/registrar/registrar-auth.service.ts:76
|
||||||
|
Get or refresh the access token for a tenant using the ROPC flow.
Parameters :
Returns :
Promise<string>
|
| Async getClient | ||||||
getClient(tenantId: string)
|
||||||
|
Defined in src/registrar/registrar-auth.service.ts:127
|
||||||
|
Create a configured registrar API client for the given tenant.
Parameters :
Returns :
unknown
|
| Async getRelyingPartyId | ||||||
getRelyingPartyId(tenantId: string)
|
||||||
|
Defined in src/registrar/registrar-auth.service.ts:150
|
||||||
|
Get the relying party ID from the registrar. If none exists yet, one is registered on the fly.
Parameters :
Returns :
Promise<string>
|
| invalidateToken | ||||||
invalidateToken(tenantId: string)
|
||||||
|
Defined in src/registrar/registrar-auth.service.ts:179
|
||||||
|
Remove the cached token for a tenant (e.g. after config changes).
Parameters :
Returns :
void
|
| Async testCredentials | ||||||
testCredentials(config: literal type)
|
||||||
|
Defined in src/registrar/registrar-auth.service.ts:45
|
||||||
|
Test OIDC credentials by attempting to obtain an access token.
Parameters :
Returns :
Promise<void>
|
| Private Readonly logger |
Type : unknown
|
Default value : new Logger(RegistrarAuthService.name)
|
|
Defined in src/registrar/registrar-auth.service.ts:32
|
| Private Readonly tokenCache |
Type : unknown
|
Default value : new Map<string, CachedToken>()
|
|
Defined in src/registrar/registrar-auth.service.ts:34
|
import { OAuth2Client, OAuth2Token } from "@badgateway/oauth2-client";
import {
BadRequestException,
Injectable,
Logger,
NotFoundException,
} from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { RegistrarConfigEntity } from "./entities/registrar-config.entity";
import {
relyingPartyControllerFindAll,
relyingPartyControllerRegister,
} from "./generated";
import { client as registrarClient } from "./generated/client.gen";
/**
* Cached OAuth2 token with its expiration time.
*/
interface CachedToken {
token: string;
expiresAt: number;
}
/**
* Handles OAuth2 token acquisition/caching and low-level registrar client
* creation. Also provides {@link getRelyingPartyId} which is shared by both
* access-certificate and registration-certificate flows.
*/
@Injectable()
export class RegistrarAuthService {
private readonly logger = new Logger(RegistrarAuthService.name);
private readonly tokenCache = new Map<string, CachedToken>();
constructor(
@InjectRepository(RegistrarConfigEntity)
private readonly configRepository: Repository<RegistrarConfigEntity>,
) {}
/**
* Test OIDC credentials by attempting to obtain an access token.
* @throws BadRequestException if authentication fails
*/
async testCredentials(config: {
oidcUrl: string;
clientId: string;
clientSecret?: string;
username: string;
password: string;
}): Promise<void> {
const oauth2Client = new OAuth2Client({
server: `${config.oidcUrl}/protocol/openid-connect/token`,
clientId: config.clientId,
clientSecret: config.clientSecret,
discoveryEndpoint: `${config.oidcUrl}/.well-known/openid-configuration`,
});
try {
await oauth2Client.password({
username: config.username,
password: config.password,
});
this.logger.log("Registrar credentials validated successfully");
} catch (error: any) {
this.logger.warn(`Credential validation failed: ${error.message}`);
throw new BadRequestException(
`Failed to authenticate with registrar. Please check your credentials. Error: ${error.message}`,
);
}
}
/**
* Get or refresh the access token for a tenant using the ROPC flow.
*/
async getAccessToken(tenantId: string): Promise<string> {
const cached = this.tokenCache.get(tenantId);
if (cached && cached.expiresAt > Date.now() + 5000) {
return cached.token;
}
const config = await this.configRepository.findOneBy({ tenantId });
if (!config) {
throw new NotFoundException(
`No registrar configuration found for tenant ${tenantId}`,
);
}
const oauth2Client = new OAuth2Client({
server: `${config.oidcUrl}/protocol/openid-connect/token`,
clientId: config.clientId,
clientSecret: config.clientSecret,
discoveryEndpoint: `${config.oidcUrl}/.well-known/openid-configuration`,
});
let tokenResponse: OAuth2Token;
try {
tokenResponse = await oauth2Client.password({
username: config.username,
password: config.password,
});
} catch (error: any) {
this.logger.error(
`[${tenantId}] Failed to obtain access token: ${error.message}`,
);
throw new BadRequestException(
`Failed to authenticate with registrar: ${error.message}`,
);
}
const expiresAt =
typeof tokenResponse.expiresAt === "number"
? tokenResponse.expiresAt
: Date.now() + 3600 * 1000;
this.tokenCache.set(tenantId, {
token: tokenResponse.accessToken,
expiresAt,
});
return tokenResponse.accessToken;
}
/**
* Create a configured registrar API client for the given tenant.
*/
async getClient(tenantId: string) {
const config = await this.configRepository.findOneBy({ tenantId });
if (!config) {
throw new NotFoundException(
`No registrar configuration found for tenant ${tenantId}`,
);
}
const client = registrarClient;
const accessToken = await this.getAccessToken(tenantId);
client.setConfig({
baseUrl: config.registrarUrl,
auth: () => accessToken,
});
return client;
}
/**
* Get the relying party ID from the registrar.
* If none exists yet, one is registered on the fly.
*/
async getRelyingPartyId(tenantId: string): Promise<string> {
const client = await this.getClient(tenantId);
const res = await relyingPartyControllerFindAll({ client });
if (res.error) {
this.logger.error(
{ error: res.error },
`[${tenantId}] Failed to fetch relying parties`,
);
throw new BadRequestException(
"Failed to fetch relying parties from registrar",
);
}
const relyingParties = res.data || [];
if (relyingParties.length === 0) {
const createRes = await relyingPartyControllerRegister({
client,
body: {},
});
return createRes.data!.id;
}
return relyingParties[0].id;
}
/**
* Remove the cached token for a tenant (e.g. after config changes).
*/
invalidateToken(tenantId: string): void {
this.tokenCache.delete(tenantId);
}
}