|
@@ -638,7 +638,7 @@ class PassportService implements S2sMessageHandlable {
|
|
|
: configManager.getConfig('crowi', 'security:passport-oidc:callbackUrl'); // DEPRECATED: backward compatible with v3.2.3 and below
|
|
: configManager.getConfig('crowi', 'security:passport-oidc:callbackUrl'); // DEPRECATED: backward compatible with v3.2.3 and below
|
|
|
|
|
|
|
|
// Prevent request timeout error on app init
|
|
// Prevent request timeout error on app init
|
|
|
- const oidcIssuer = await this.getOIDCIssuerInstace(issuerHost);
|
|
|
|
|
|
|
+ const oidcIssuer = await this.getOIDCIssuerInstance(issuerHost);
|
|
|
if (oidcIssuer != null) {
|
|
if (oidcIssuer != null) {
|
|
|
logger.debug('Discovered issuer %s %O', oidcIssuer.issuer, oidcIssuer.metadata);
|
|
logger.debug('Discovered issuer %s %O', oidcIssuer.issuer, oidcIssuer.metadata);
|
|
|
|
|
|
|
@@ -719,16 +719,26 @@ class PassportService implements S2sMessageHandlable {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Sanitize issuer Host / URL to match specified format
|
|
* Sanitize issuer Host / URL to match specified format
|
|
|
- * Acceptable format : eg. https://hostname.com
|
|
|
|
|
|
|
+ * Acceptable formats :
|
|
|
|
|
+ * - https://hostname.com/auth/
|
|
|
|
|
+ * - domain only (hostname.com)
|
|
|
|
|
+ * - Full metadata url (https://hostname.com/auth/v2/.well-known/openid-configuration)
|
|
|
* @param issuerHost string
|
|
* @param issuerHost string
|
|
|
- * @returns string URL.origin
|
|
|
|
|
|
|
+ * @returns string URL/.well-known/openid-configuration
|
|
|
*/
|
|
*/
|
|
|
- getOIDCIssuerHostName(issuerHost) {
|
|
|
|
|
|
|
+ getOIDCMetadataURL(issuerHost: string) : string {
|
|
|
const protocol = 'https://';
|
|
const protocol = 'https://';
|
|
|
const pattern = /^https?:\/\//i;
|
|
const pattern = /^https?:\/\//i;
|
|
|
|
|
+ const metadataPath = '/.well-known/openid-configuration';
|
|
|
|
|
+ // If URL is full path with .well-known/openid-configuration
|
|
|
|
|
+ if (issuerHost.endsWith(metadataPath)) {
|
|
|
|
|
+ return issuerHost;
|
|
|
|
|
+ }
|
|
|
// Set protocol if not available on url
|
|
// Set protocol if not available on url
|
|
|
const absUrl = !pattern.test(issuerHost) ? `${protocol}${issuerHost}` : issuerHost;
|
|
const absUrl = !pattern.test(issuerHost) ? `${protocol}${issuerHost}` : issuerHost;
|
|
|
- return new URL(absUrl).origin;
|
|
|
|
|
|
|
+ const url = new URL(absUrl).href;
|
|
|
|
|
+ // Remove trailing slash if exists
|
|
|
|
|
+ return `${url.replace(/\/+$/, '')}${metadataPath}`;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -736,17 +746,17 @@ class PassportService implements S2sMessageHandlable {
|
|
|
* Check and initialize connection to OIDC issuer host
|
|
* Check and initialize connection to OIDC issuer host
|
|
|
* Prevent request timeout error on app init
|
|
* Prevent request timeout error on app init
|
|
|
*
|
|
*
|
|
|
- * @param issuerHost
|
|
|
|
|
|
|
+ * @param issuerHost string
|
|
|
* @returns boolean
|
|
* @returns boolean
|
|
|
*/
|
|
*/
|
|
|
- async isOidcHostReachable(issuerHost) {
|
|
|
|
|
|
|
+ async isOidcHostReachable(issuerHost: string): Promise<boolean | undefined> {
|
|
|
try {
|
|
try {
|
|
|
- const hostname = this.getOIDCIssuerHostName(issuerHost);
|
|
|
|
|
|
|
+ const metadataUrl = this.getOIDCMetadataURL(issuerHost);
|
|
|
const client = require('axios').default;
|
|
const client = require('axios').default;
|
|
|
axiosRetry(client, {
|
|
axiosRetry(client, {
|
|
|
retries: 3,
|
|
retries: 3,
|
|
|
});
|
|
});
|
|
|
- const response = await client.get(`${hostname}/.well-known/openid-configuration`);
|
|
|
|
|
|
|
+ const response = await client.get(metadataUrl);
|
|
|
// Check for valid OIDC Issuer configuration
|
|
// Check for valid OIDC Issuer configuration
|
|
|
if (!response.data.issuer) {
|
|
if (!response.data.issuer) {
|
|
|
logger.debug('OidcStrategy: Invalid OIDC Issuer configurations');
|
|
logger.debug('OidcStrategy: Invalid OIDC Issuer configurations');
|
|
@@ -763,10 +773,10 @@ class PassportService implements S2sMessageHandlable {
|
|
|
* Get oidcIssuer object
|
|
* Get oidcIssuer object
|
|
|
* Utilize p-retry package to retry oidcIssuer initialization 3 times
|
|
* Utilize p-retry package to retry oidcIssuer initialization 3 times
|
|
|
*
|
|
*
|
|
|
- * @param issuerHost
|
|
|
|
|
|
|
+ * @param issuerHost string
|
|
|
* @returns instance of OIDCIssuer
|
|
* @returns instance of OIDCIssuer
|
|
|
*/
|
|
*/
|
|
|
- async getOIDCIssuerInstace(issuerHost) {
|
|
|
|
|
|
|
+ async getOIDCIssuerInstance(issuerHost: string): Promise<void | OIDCIssuer> {
|
|
|
const OIDC_TIMEOUT_MULTIPLIER = await this.crowi.configManager.getConfig('crowi', 'security:passport-oidc:timeoutMultiplier');
|
|
const OIDC_TIMEOUT_MULTIPLIER = await this.crowi.configManager.getConfig('crowi', 'security:passport-oidc:timeoutMultiplier');
|
|
|
const OIDC_DISCOVERY_RETRIES = await this.crowi.configManager.getConfig('crowi', 'security:passport-oidc:discoveryRetries');
|
|
const OIDC_DISCOVERY_RETRIES = await this.crowi.configManager.getConfig('crowi', 'security:passport-oidc:discoveryRetries');
|
|
|
const OIDC_ISSUER_TIMEOUT_OPTION = await this.crowi.configManager.getConfig('crowi', 'security:passport-oidc:oidcIssuerTimeoutOption');
|
|
const OIDC_ISSUER_TIMEOUT_OPTION = await this.crowi.configManager.getConfig('crowi', 'security:passport-oidc:oidcIssuerTimeoutOption');
|
|
@@ -775,8 +785,9 @@ class PassportService implements S2sMessageHandlable {
|
|
|
logger.error('OidcStrategy: setup failed');
|
|
logger.error('OidcStrategy: setup failed');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
+ const metadataURL = this.getOIDCMetadataURL(issuerHost);
|
|
|
const oidcIssuer = await pRetry(async() => {
|
|
const oidcIssuer = await pRetry(async() => {
|
|
|
- return OIDCIssuer.discover(issuerHost);
|
|
|
|
|
|
|
+ return OIDCIssuer.discover(metadataURL);
|
|
|
}, {
|
|
}, {
|
|
|
onFailedAttempt: (error) => {
|
|
onFailedAttempt: (error) => {
|
|
|
// get current OIDCIssuer timeout options
|
|
// get current OIDCIssuer timeout options
|