auth-backend: refactor identity APIs for forwards compatibility
This commit is contained in:
@@ -34,10 +34,7 @@ exports.up = async function up(knex) {
|
||||
.notNullable()
|
||||
.defaultTo(knex.fn.now())
|
||||
.comment('The creation time of the key');
|
||||
table
|
||||
.string('key')
|
||||
.notNullable()
|
||||
.comment('The serialized public part of the signing key');
|
||||
table.string('key').notNullable().comment('The serialized signing key');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import Knex from 'knex';
|
||||
import path from 'path';
|
||||
import { utc } from 'moment';
|
||||
import { Logger } from 'winston';
|
||||
import { PublicKey } from './types';
|
||||
import { AnyJWK, KeyStore } from './types';
|
||||
|
||||
const migrationsDir = path.resolve(
|
||||
require.resolve('@backstage/plugin-auth-backend/package.json'),
|
||||
@@ -40,7 +40,7 @@ type Options = {
|
||||
keyDuration: number;
|
||||
};
|
||||
|
||||
export class DatabaseKeyStore {
|
||||
export class DatabaseKeyStore implements KeyStore {
|
||||
static async create(options: Options): Promise<DatabaseKeyStore> {
|
||||
const { database } = options;
|
||||
|
||||
@@ -65,7 +65,7 @@ export class DatabaseKeyStore {
|
||||
this.logger = logger.child({ service: 'key-store' });
|
||||
}
|
||||
|
||||
async addPublicKey(key: PublicKey): Promise<void> {
|
||||
async storeKey({ key }: { key: AnyJWK }): Promise<void> {
|
||||
this.logger.info(`Storing public key ${key.kid}`);
|
||||
|
||||
await this.database<Row>(TABLE).insert({
|
||||
@@ -74,7 +74,7 @@ export class DatabaseKeyStore {
|
||||
});
|
||||
}
|
||||
|
||||
async listPublicKeys(): Promise<PublicKey[]> {
|
||||
async listKeys(): Promise<{ keys: AnyJWK[] }> {
|
||||
const rows = await this.database<Row>(TABLE).select();
|
||||
|
||||
const [validRows, expiredRows] = this.splitExpiredRows(rows);
|
||||
@@ -83,7 +83,9 @@ export class DatabaseKeyStore {
|
||||
this.removeExpiredRows(expiredRows);
|
||||
}
|
||||
|
||||
return validRows.map(row => JSON.parse(row.key));
|
||||
return {
|
||||
keys: validRows.map(row => JSON.parse(row.key)),
|
||||
};
|
||||
}
|
||||
|
||||
private splitExpiredRows(rows: Row[]) {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TokenIssuer, TokenParams, KeyStore, PublicKey } from './types';
|
||||
import { TokenIssuer, TokenParams, KeyStore, AnyJWK } from './types';
|
||||
import { JSONWebKey, JWK, JWS } from 'jose';
|
||||
import { Logger } from 'winston';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@@ -45,11 +45,11 @@ export class TokenFactory implements TokenIssuer {
|
||||
this.keyDuration = options.keyDuration;
|
||||
}
|
||||
|
||||
async issueToken(claims: TokenParams): Promise<string> {
|
||||
async issueToken(params: TokenParams): Promise<string> {
|
||||
const key = await this.getKey();
|
||||
|
||||
const iss = this.issuer;
|
||||
const sub = claims.sub;
|
||||
const sub = params.claims.sub;
|
||||
const aud = 'backstage';
|
||||
const iat = (Date.now() / 1000) | 0;
|
||||
const exp = iat + 3600;
|
||||
@@ -79,9 +79,9 @@ export class TokenFactory implements TokenIssuer {
|
||||
alg: 'ES256',
|
||||
});
|
||||
|
||||
await this.keyStore.addPublicKey(
|
||||
(key.toJWK(false) as unknown) as PublicKey,
|
||||
);
|
||||
await this.keyStore.storeKey({
|
||||
key: (key.toJWK(false) as unknown) as AnyJWK,
|
||||
});
|
||||
|
||||
return key as JSONWebKey;
|
||||
})();
|
||||
|
||||
@@ -50,7 +50,7 @@ export function createOidcRouter(options: Options) {
|
||||
|
||||
router.get('/.well-known/jwks.json', async (_req, res) => {
|
||||
logger.info('request certs');
|
||||
const keys = await keyStore.listPublicKeys();
|
||||
const { keys } = await keyStore.listKeys();
|
||||
res.json({ keys });
|
||||
});
|
||||
|
||||
|
||||
@@ -14,23 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface PublicKey extends Record<string, string> {
|
||||
use: 'sig' | 'enc';
|
||||
export interface AnyJWK extends Record<string, string> {
|
||||
use: 'sig';
|
||||
alg: string;
|
||||
kid: string;
|
||||
kty: string;
|
||||
}
|
||||
|
||||
export type KeyStore = {
|
||||
addPublicKey(key: PublicKey): Promise<void>;
|
||||
storeKey(params: { key: AnyJWK }): Promise<void>;
|
||||
|
||||
listPublicKeys(): Promise<PublicKey[]>;
|
||||
listKeys(): Promise<{ keys: AnyJWK[] }>;
|
||||
};
|
||||
|
||||
export type TokenParams = {
|
||||
sub: string;
|
||||
claims: {
|
||||
sub: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TokenIssuer = {
|
||||
issueToken(claims: TokenParams): Promise<string>;
|
||||
issueToken(params: TokenParams): Promise<string>;
|
||||
};
|
||||
|
||||
@@ -145,7 +145,7 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
}
|
||||
|
||||
user.userIdToken = await this.options.tokenIssuer.issueToken({
|
||||
sub: user.profile.email,
|
||||
claims: { sub: user.profile.email },
|
||||
});
|
||||
|
||||
// post message back to popup if successful
|
||||
@@ -206,7 +206,7 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
);
|
||||
|
||||
refreshInfo.userIdToken = await this.options.tokenIssuer.issueToken({
|
||||
sub: refreshInfo.profile?.email,
|
||||
claims: { sub: refreshInfo.profile?.email },
|
||||
});
|
||||
|
||||
return res.send(refreshInfo);
|
||||
|
||||
Reference in New Issue
Block a user