Source: authprovider.js

const Environment = require('./env');
const Utils = require('@openeo/js-commons/src/utils');
const OidcClient = require('oidc-client');

/**
 * The base class for authentication providers such as Basic and OpenID Connect.
 * 
 * @class
 * @abstract
 */
class AuthProvider {

	constructor(type, connection, options) {
		Object.assign(this, options);
		this.type = type;
		this.connection = connection;
		this.token = null;
	}

	getId() {
		let id = this.getType();
		if (this.getProviderId().length > 0) {
			id += '.' + this.getProviderId();
		}
		return id;
	}

	getType() {
		return this.type;
	}

	getProviderId() {
		return typeof this.id === 'string' ? this.id : "";
	}

	getTitle() {
		return this.title;
	}

	getDescription() {
		return this.description;
	}

	getToken() {
		if (typeof this.token === 'string') {
			return this.getType() + "/" + this.getProviderId() + "/" + this.token;
		}
		else {
			return null;
		}
	}

	setToken(token) {
		this.token = token;
		if (this.token !== null) {
			this.connection.authProvider = this;
		}
		else {
			this.connection.authProvider = null;
		}
	}

	async login() {
		throw "Not implemented.";
	}

	/**
	 * Logout from the established session - EXPERIMENTAL!
	 * 
	 * @async
	 */
	async logout() {
		this.setToken(null);
	}

}

/**
 * The Authentication Provider for OpenID Connect.
 * 
 * ToDo: Add how to use the OIDC Provider.
 * 
 * @class
 * @extends {AuthProvider}
 */
class OidcProvider extends AuthProvider {

	constructor(connection, options) {
		super("oidc", connection, options);
		this.manager = null;
		this.user = null;
	}

	static isSupported() {
		return (Utils.isObject(OidcClient) && !!OidcClient.UserManager);
	}

	/**
	 * Globally sets the UI method (redirect, popup) to use for OIDC authentication.
	 * 
	 * @static
	 * @param {string} method - Method how to load and show the authentication process. Either `popup` (opens a popup window) or `redirect` (HTTP redirects, default).
	 */
	static setUiMethod(method) {
		OidcProvider.uiMethod = method;
	}

	/**
	 * Finishes the OpenID Connect sign in (authentication) workflow - EXPERIMENTAL!
	 * 
	 * Must be called in the page that OpenID Connect redirects to after logging in.
	 * 
	 * @async
	 * @static
	 * @param {OidcProvider} provider - A OIDC provider to assign the user to.
	 * @returns {User} For uiMethod = 'redirect' only: OIDC User (to be assigned to the Connection via setUser if no provider has been specified). 
	 * @throws Error
	 */
	static async signinCallback(provider = null) {
		var oidc = new OidcClient.UserManager();
		if (OidcProvider.uiMethod === 'popup') {
			await oidc.signinPopupCallback();
		}
		else {
			let user = await oidc.signinRedirectCallback();
			if (provider) {
				provider.setUser(user);
			}
			return user;
		}
	}

	/**
	 * Authenticate with OpenID Connect (OIDC) - EXPERIMENTAL!
	 * 
	 * Supported only in Browser environments.
	 * 
	 * @param {string} client_id - Your client application's identifier as registered with the OIDC provider
	 * @param {string} redirect_uri - The redirect URI of your client application to receive a response from the OIDC provider.
	 * @param {object} [options={}] - Object with authentication options. See https://github.com/IdentityModel/oidc-client-js/wiki#other-optional-settings for further options.
	 * @throws {Error}
	 */
	async login(client_id, redirect_uri, options = {}) {
		if (!this.issuer || typeof this.issuer !== 'string') {
			throw "No Issuer URL available for OpenID Connect";
		}
		else if (!client_id || typeof client_id !== 'string') {
			throw "No Client ID specified for OpenID Connect";
		}
		else if (!redirect_uri || typeof redirect_uri !== 'string') {
			throw "No Redirect URI specified for OpenID Connect";
		}

		this.manager = new OidcClient.UserManager(Object.assign({
			client_id: client_id,
			redirect_uri: redirect_uri,
			authority: this.issuer.replace('/.well-known/openid-configuration', ''),
			response_type: 'token id_token',
			scope: this.getScopes().join(' ')
		}, options));

		if (OidcProvider.uiMethod === 'popup') {
			this.setUser(await this.manager.signinPopup());
		}
		else {
			await this.manager.signinRedirect();
		}
	}

	getScopes() {
		return Array.isArray(this.scopes) && this.scopes.length > 0 ? this.scopes : ['openid'];
	}

	getIssuer() {
		return this.issuer;
	}

	getUser() {
		return this.user;
	}

	/**
	 * Sets the OIDC User.
	 * 
	 * @see https://github.com/IdentityModel/oidc-client-js/wiki#user
	 * @param {User} user - The OIDC User returned by OidcProvider.signinCallback(). Passing `null` resets OIDC authentication details.
	 */
	setUser(user) {
		if (!user) {
			this.user = null;
			this.setToken(null);
		}
		else {
			this.user = user;
			this.setToken(user.access_token);
		}
	}

	/**
	 * Logout from the established session - EXPERIMENTAL!
	 * 
	 * @async
	 */
	async logout() {
		if (this.manager !== null) {
			try {
				await this.manager.signoutRedirect();
			} catch (error) {
				console.warn(error);
			}
			super.logout();
			this.manager = null;
			this.setUser(null);
		}
	}

}
OidcProvider.uiMethod = 'redirect';


/**
 * The Authentication Provider for HTTP Basic.
 * 
 * @class
 * @extends {AuthProvider}
 */
class BasicProvider extends AuthProvider {

	constructor(connection) {
		super("basic", connection, {
			title: "HTTP Basic",
			description: "Login with username and password using the method HTTP Basic."
		});
	}

	/**
	 * Authenticate with HTTP Basic.
	 * 
	 * @async
	 * @param {object} options - Options for Basic authentication.
	 * @throws {Error}
	 */
	async login(username, password) {
		let response = await this.connection._send({
			method: 'get',
			responseType: 'json',
			url: '/credentials/basic',
			headers: {'Authorization': 'Basic ' + Environment.base64encode(username + ':' + password)}
		});
		if (!Utils.isObject(response.data) || typeof response.data.access_token !== 'string') {
			throw new Error("No access_token returned.");
		}
		this.setToken(response.data.access_token);
	}

}

module.exports = {
	AuthProvider,
	BasicProvider,
	OidcProvider
};