Skip to content

Commit

Permalink
Add realmAuthServerUrlResolver, closes #174
Browse files Browse the repository at this point in the history
  • Loading branch information
ferrerojosh committed Mar 12, 2024
1 parent 9c80edc commit 43977c7
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 8 deletions.
12 changes: 11 additions & 1 deletion src/interface/keycloak-connect-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,17 @@ export interface MultiTenantOptions {
/**
* The realm secret resolver function.
*/
realmSecretResolver?: (realm: string) => Promise<string> | string;
realmSecretResolver?: (
realm: string,
request?: any,
) => Promise<string> | string;
/**
* The realm auth server url resolver function.
*/
realmAuthServerUrlResolver?: (
realm: string,
request?: any,
) => Promise<string> | string;
}

/**
Expand Down
79 changes: 74 additions & 5 deletions src/services/keycloak-multitenant.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ export class KeycloakMultiTenantService {
/**
* Retrieves a keycloak instance based on the realm provided.
* @param realm the realm to retrieve from
* @param request the request instance, defaults to undefined
* @returns the multi tenant keycloak instance
*/
async get(realm: string): Promise<KeycloakConnect.Keycloak> {
async get(
realm: string,
request: any = undefined,
): Promise<KeycloakConnect.Keycloak> {
if (typeof this.keycloakOpts === 'string') {
throw new Error(
'Keycloak configuration is a configuration path. This should not happen after module load.',
Expand All @@ -42,11 +46,16 @@ export class KeycloakMultiTenantService {
);
}

const authServerUrl = await this.resolveAuthServerUrl(realm, request);
const secret = await this.resolveSecret(realm, request);

// Check if existing
if (this.instances.has(realm)) {
// If resolve always is enabled, resolve everything again
if (this.keycloakOpts.multiTenant.resolveAlways) {
const keycloak: any = this.instances.get(realm);
const secret = await this.resolveSecret(realm);

keycloak.config.authServerUrl = authServerUrl;
keycloak.config.secret = secret;
keycloak.grantManager.secret = secret;

Expand All @@ -55,12 +64,13 @@ export class KeycloakMultiTenantService {

return keycloak;
}
// Otherwise return the instance
return this.instances.get(realm);
} else {
const secret = await this.resolveSecret(realm);
// TODO: Repeating code from provider, will need to rework this in 2.0
// Override realm and secret
// Override realm, secret, and authServerUrl
const keycloakOpts: any = Object.assign(this.keycloakOpts, {
authServerUrl,
realm,
secret,
});
Expand All @@ -72,12 +82,65 @@ export class KeycloakMultiTenantService {
next();
};

// Save instance
this.instances.set(realm, keycloak);
return keycloak;
}
}

async resolveSecret(realm: string): Promise<string> {
async resolveAuthServerUrl(
realm: string,
request: any = undefined,
): Promise<string> {
if (typeof this.keycloakOpts === 'string') {
throw new Error(
'Keycloak configuration is a configuration path. This should not happen after module load.',
);
}
if (
this.keycloakOpts.multiTenant === null ||
this.keycloakOpts.multiTenant === undefined
) {
throw new Error(
'Multi tenant is not defined yet multi tenant service is being called.',
);
}

// If no realm auth server url resolver is defined, return defaults
if (!this.keycloakOpts.multiTenant.realmAuthServerUrlResolver) {
return (
this.keycloakOpts.authServerUrl ||
this.keycloakOpts['auth-server-url'] ||
this.keycloakOpts.serverUrl ||
this.keycloakOpts['server-url']
);
}

// Resolve realm authServerUrl
const resolvedAuthServerUrl = this.keycloakOpts.multiTenant.realmAuthServerUrlResolver(
realm,
request,
);
const authServerUrl =
resolvedAuthServerUrl || resolvedAuthServerUrl instanceof Promise
? await resolvedAuthServerUrl
: resolvedAuthServerUrl;

// Override auth server url
// Order of priority: resolved realm auth server url > provided auth server url
return (
authServerUrl ||
this.keycloakOpts.authServerUrl ||
this.keycloakOpts['auth-server-url'] ||
this.keycloakOpts.serverUrl ||
this.keycloakOpts['server-url']
);
}

async resolveSecret(
realm: string,
request: any = undefined,
): Promise<string> {
if (typeof this.keycloakOpts === 'string') {
throw new Error(
'Keycloak configuration is a configuration path. This should not happen after module load.',
Expand All @@ -92,9 +155,15 @@ export class KeycloakMultiTenantService {
);
}

// If no realm secret resolver is defined, return defaults
if (!this.keycloakOpts.multiTenant.realmSecretResolver) {
return this.keycloakOpts.secret;
}

// Resolve realm secret
const resolvedRealmSecret = this.keycloakOpts.multiTenant.realmSecretResolver(
realm,
request,
);
const realmSecret =
resolvedRealmSecret || resolvedRealmSecret instanceof Promise
Expand Down
4 changes: 2 additions & 2 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ export const useKeycloak = async (
const resolvedRealm = opts.multiTenant.realmResolver(request);
const realm =
resolvedRealm instanceof Promise ? await resolvedRealm : resolvedRealm;
return await multiTenant.get(realm);
return await multiTenant.get(realm, request);
} else if (!opts.realm) {
const payload = parseToken(jwt);
const issuerRealm = payload.iss.split('/').pop();
return await multiTenant.get(issuerRealm);
return await multiTenant.get(issuerRealm, request);
}
return singleTenant;
};
Expand Down

0 comments on commit 43977c7

Please sign in to comment.