* empty commit
* Add missing copyright headers
* chore: 🤖 update copywrite config file with correct year
* Add missing copyright headers
* empty commit (gh action merge checks are stuck)
121 lines
4.6 KiB
JavaScript
121 lines
4.6 KiB
JavaScript
/**
|
|
* Copyright IBM Corp. 2021, 2026
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import BaseAuthenticator from './base';
|
|
import { service } from '@ember/service';
|
|
import { reject } from 'rsvp';
|
|
import { waitForPromise } from '@ember/test-waiters';
|
|
|
|
/**
|
|
* The OIDC base authenticator encapsulates the multi-step OIDC flow.
|
|
*
|
|
* 1. Start authentication flow: this step is actually a combination of two
|
|
* sub steps:
|
|
* 1. Initiate a call via `startAuthentication` to the API at
|
|
* `buildAuthEndpointURL` using the `start` command to fetch the URL of
|
|
* the external authentication provider (`auth_url`).
|
|
* 2. Open `auth_url`. This may be either inline (same tab)
|
|
* or external in a new tab or browser window.
|
|
* 2. Requests the token from `buildAuthEndpointURL` using the `token` command.
|
|
* A token isn't guaranteed to be ready, so the OIDC flow can be in a
|
|
* "pending" state.
|
|
*
|
|
* This authenticator should not be used directly because it does not specify
|
|
* URL-building endpoints of its own.
|
|
* To use, generate an application authenticator in your app
|
|
* `authenticators/application.js` and extend this class.
|
|
*/
|
|
export default class OIDCAuthenticator extends BaseAuthenticator {
|
|
// =services
|
|
|
|
@service session;
|
|
|
|
// =methods
|
|
|
|
/**
|
|
* Kicks-off the OIDC flow: calls the `authenticate:start` action on the
|
|
* specified auth method, which returns some meta data along with an
|
|
* authorization request URL. The response data is saved for later and
|
|
* authentication enters a pending state.
|
|
*
|
|
* @param {boolean} requestCookies request cookie tokens (default `true`)
|
|
* @param {object} options
|
|
* @return {object}
|
|
*/
|
|
async startAuthentication(options) {
|
|
const url = this.buildAuthEndpointURL(options);
|
|
const body = JSON.stringify({ command: 'start' });
|
|
// Note: waitForPromise is needed to provide the necessary integration with @ember/test-helpers
|
|
// visit https://www.npmjs.com/package/@ember/test-waiters for more info.
|
|
const response = await waitForPromise(fetch(url, { method: 'post', body }));
|
|
const json = await response.json();
|
|
if (response.status < 400) {
|
|
// Store meta about the pending OIDC flow
|
|
this.session.set('data.pending', {
|
|
oidc: json,
|
|
});
|
|
return json;
|
|
} else {
|
|
return reject();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* OIDC is a _process_ and does not have a singular "authenticate" step. Thus
|
|
* this authenticate method simply normalizes the data passed into it once
|
|
* `attemptFetchToken` completes. Notably, no requests are made by calling
|
|
* authenticate. A Boundary token must already have been obtained.
|
|
* @param {object} json
|
|
* @return {object}
|
|
*/
|
|
async authenticate(json) {
|
|
return this.normalizeData(json);
|
|
}
|
|
|
|
/**
|
|
* This request attempts to fetch a Boundary token. This method represents a
|
|
* single request the polling sequence used to wait for authentication.
|
|
* There are three possible outcomes:
|
|
*
|
|
* 1. If the token is ready, it is persisted into the session, the session
|
|
* enters an authenticated state, and this method returns `true`.
|
|
* 2. If the token is not yet ready, this method returns false.
|
|
* 3. If the request errors, this method returns to a rejection,
|
|
* requiring appropriate error handling.
|
|
*/
|
|
async attemptFetchToken(options) {
|
|
// Get the URL and setup the request body
|
|
const url = this.buildAuthEndpointURL(options);
|
|
const body = JSON.stringify({
|
|
command: 'token',
|
|
attributes: {
|
|
//state: this.session.get('data.pending.oidc.attributes.state'),
|
|
token_id: this.session.get('data.pending.oidc.attributes.token_id'),
|
|
},
|
|
});
|
|
// Fetch the endpoint and get the response JSON
|
|
const response = await waitForPromise(fetch(url, { method: 'post', body }));
|
|
|
|
// Note: Always consume response body in order to avoid memory leaks
|
|
// visit https://undici.nodejs.org/#/?id=garbage-collection for more info.
|
|
// We do not use the undici package but the link informs us that garbage
|
|
// collection is undefined when response body is not consumed.
|
|
const json = await response.json();
|
|
|
|
if (response.status === 202) {
|
|
// The token isn't ready yet, keep trying.
|
|
return false;
|
|
} else if (response.status < 400) {
|
|
// Response was successful, meaning a token was obtained.
|
|
// Authenticate with the session service using the response JSON.
|
|
await this.session.authenticate('authenticator:oidc', json);
|
|
return true;
|
|
} else {
|
|
// Response errors, return a rejection for further handling.
|
|
return reject();
|
|
}
|
|
}
|
|
}
|