"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SmartAuthProvider = exports.AuthSecretType = void 0;
const icc_api_1 = require("../../icc-api");
const XHR_1 = require("../../icc-api/api/XHR");
var XHRError = XHR_1.XHR.XHRError;
const JwtUtils_1 = require("./JwtUtils");
/**
 * Represents a type of secret that can be used for authentication with iCure.
 */
var AuthSecretType;
(function (AuthSecretType) {
    /**
     * Password chosen by the user.
     */
    AuthSecretType["PASSWORD"] = "PASSWORD";
    /**
     * Time based one time password provided by authenticator applications, generated on the basis of a timestamp and a shared secret between the iCure
     * server and the authenticator application.
     */
    AuthSecretType["TWO_FACTOR_AUTHENTICATION_TOKEN"] = "TWO_FACTOR_AUTHENTICATION_TOKEN";
    /**
     * A short-lived iCure token, an internal authentication token that lasts 5 minutes or less. Unlike passwords these tokens usually are generated by
     * some component of iCure, and are not chosen by the user.
     */
    AuthSecretType["SHORT_LIVED_TOKEN"] = "SHORT_LIVED_TOKEN";
    /**
     * A long-lived iCure token, an internal authentication token that lasts longer than 5 minutes. Unlike passwords these tokens usually are generated
     * by some component of iCure, and are not chosen by the user.
     */
    AuthSecretType["LONG_LIVED_TOKEN"] = "LONG_LIVED_TOKEN";
    /**
     * A token provided by an external authentication provider (e.g. Oauth/Google).
     * Not yet in use.
     */
    AuthSecretType["EXTERNAL_AUTHENTICATION"] = "EXTERNAL_AUTHENTICATION";
    /**
     * A special case of external authentication where the provider is a digital identity provider.
     */
    AuthSecretType["DIGITAL_ID"] = "DIGITAL_ID";
    /**
     * The secret is an authentication token for a cloud instance of icure (kraken-cloud).
     * Supported only on lite instances.
     */
    AuthSecretType["ICURE_CLOUD"] = "ICURE_CLOUD";
})(AuthSecretType || (exports.AuthSecretType = AuthSecretType = {}));
// Here starts internal entities that should not be used directly.
/**
 * @internal this class is meant for internal use only and may be changed without notice. The SmartAuthProvider will be initialised automatically
 * by the iCure api depending on the authentication options you provide.
 *
 * An authentication provider that automatically requests secrets for authentication as needed.
 *
 * This authentication provider can be initialised already with some secrets or tokens, and the provider will cache them and use them as needed for
 * as long as they remain valid. If at any point however the provider needs an updated secret or a secret of a different kind it will automatically
 * request this to the {@link SmartAuthProvider} to get the secret.
 *
 * An advantage of using this provider over others is that in case all the cached tokens and secrets were to expire while performing a request,
 * instead of having the request fail the provider will ask for new secrets from the {@link SmartAuthProvider} and the request will automatically
 * be retried with the new secret. Additionally, the provider may request updated secrets also for performing some sensitive operations (e.g. changing
 * password of the user) even if the cached tokens and/or did not expire. This could be the case for example if the cached secret is a long-lived
 * token, but in order to change the password the user needs to provide his current password.
 *
 * Note that in this context the cache of secrets and token is in memory only, and is not persisted in any way. Different instances of this provider
 * will not share the same cache.
 *
 */
class SmartAuthProvider {
    /**
     * Initialises a {@link SmartAuthProvider}.
     * @param authApi an "anonymous" {@link IccAuthApi} to use for authentication.
     * @param login
     * @param secretProvider
     * @param props optional initialisation properties.
     */
    static initialise(authApi_1, login_1, secretProvider_1) {
        return __awaiter(this, arguments, void 0, function* (authApi, login, secretProvider, props = {}) {
            let initialSecret = undefined;
            let initialAuthToken = props.initialAuthToken;
            let initialRefreshToken = props.initialRefreshToken;
            if (props.initialSecret) {
                if ('password' in props.initialSecret) {
                    if (login == undefined)
                        throw new Error('To use a password initial secret you must provide a login identifier');
                    initialSecret = { value: props.initialSecret.password, type: ServerAuthenticationClass.PASSWORD };
                }
                else if ('longToken' in props.initialSecret) {
                    if (login == undefined)
                        throw new Error('To use a password initial secret you must provide a login identifier');
                    initialSecret = { value: props.initialSecret.longToken, type: ServerAuthenticationClass.LONG_LIVED_TOKEN };
                }
                else if ('oauthToken' in props.initialSecret) {
                    if (longLivedOAuthTokens.has(props.initialSecret.oauthType)) {
                        initialSecret = {
                            value: props.initialSecret.oauthToken,
                            type: ServerAuthenticationClass.EXTERNAL_AUTHENTICATION,
                            oauthType: props.initialSecret.oauthType,
                        };
                    }
                    else {
                        try {
                            const authResponse = yield authApi.loginWithThirdPartyToken(props.initialSecret.oauthType, props.initialSecret.oauthToken, props.loginGroupId);
                            if (authResponse.token != undefined && authResponse.refreshToken != undefined) {
                                initialAuthToken = authResponse.token;
                                initialRefreshToken = authResponse.refreshToken;
                            }
                        }
                        catch (_) {
                            /* ignored */
                        }
                    }
                }
                else if ('digitalIdToken' in props.initialSecret) {
                    try {
                        const authResponse = yield authApi.loginWithThirdPartyToken(props.initialSecret.provider, props.initialSecret.digitalIdToken, props.loginGroupId);
                        if (authResponse.token != undefined && authResponse.refreshToken != undefined) {
                            initialAuthToken = authResponse.token;
                            initialRefreshToken = authResponse.refreshToken;
                        }
                    }
                    catch (_) {
                        /* ignored */
                    }
                }
                else {
                    try {
                        const authResponse = yield authApi.loginWithCloudToken(props.initialSecret.issuer, props.initialSecret.cloudToken);
                        if (authResponse.token != undefined && authResponse.refreshToken != undefined) {
                            initialAuthToken = authResponse.token;
                            initialRefreshToken = authResponse.refreshToken;
                        }
                    }
                    catch (_) {
                        /* ignored */
                    }
                }
            }
            return new SmartAuthProvider(new TokenProvider(login, props.loginGroupId, initialSecret, initialAuthToken, initialRefreshToken, authApi, secretProvider, props.debugLog != undefined ? props.debugLog : true), props.loginGroupId);
        });
    }
    constructor(tokenProvider, groupId) {
        this.tokenProvider = tokenProvider;
        this.groupId = groupId;
    }
    getAuthService() {
        return new SmartAuthService(this.tokenProvider);
    }
    switchGroup(newGroupId, matches) {
        return __awaiter(this, void 0, void 0, function* () {
            if (newGroupId == this.groupId)
                return Promise.resolve(this);
            if (!matches.find((match) => match.groupId == newGroupId))
                throw new Error('New group id not found in matches.');
            const switchedProvider = yield this.tokenProvider.switchedGroup(newGroupId);
            return new SmartAuthProvider(switchedProvider, this.groupId);
        });
    }
    getIcureTokens() {
        return this.tokenProvider.getCachedTokensOrLoad();
    }
}
exports.SmartAuthProvider = SmartAuthProvider;
var ServerAuthenticationClass;
(function (ServerAuthenticationClass) {
    ServerAuthenticationClass[ServerAuthenticationClass["DIGITAL_ID"] = 60] = "DIGITAL_ID";
    ServerAuthenticationClass[ServerAuthenticationClass["TWO_FACTOR_AUTHENTICATION"] = 50] = "TWO_FACTOR_AUTHENTICATION";
    ServerAuthenticationClass[ServerAuthenticationClass["SHORT_LIVED_TOKEN"] = 40] = "SHORT_LIVED_TOKEN";
    ServerAuthenticationClass[ServerAuthenticationClass["EXTERNAL_AUTHENTICATION"] = 30] = "EXTERNAL_AUTHENTICATION";
    ServerAuthenticationClass[ServerAuthenticationClass["PASSWORD"] = 20] = "PASSWORD";
    ServerAuthenticationClass[ServerAuthenticationClass["LONG_LIVED_TOKEN"] = 10] = "LONG_LIVED_TOKEN";
})(ServerAuthenticationClass || (ServerAuthenticationClass = {}));
// In some providers Oauth tokens may have short duration or may be usable only once. We only want to cache them if they are going to be reusable and
// if they last more than 5 minutes.
const longLivedOAuthTokens = new Set([icc_api_1.OAuthThirdParty.GOOGLE]);
class TokenProvider {
    constructor(login, groupId, currentLongLivedSecret, cachedToken, cachedRefreshToken, authApi, authSecretProvider, debugLog) {
        this.login = login;
        this.groupId = groupId;
        this.currentLongLivedSecret = currentLongLivedSecret;
        this.cachedToken = cachedToken;
        this.cachedRefreshToken = cachedRefreshToken;
        this.authApi = authApi;
        this.authSecretProvider = authSecretProvider;
        this.debugLog = debugLog;
    }
    debugCacheInfo() {
        var _a, _b;
        return `\n\tcached bearer exp: ${this.cachedToken ? (0, JwtUtils_1.decodeJwtClaims)(this.cachedToken)['exp'] : 'none'}\n\tcached refresh exp ${this.cachedRefreshToken ? (0, JwtUtils_1.decodeJwtClaims)(this.cachedRefreshToken)['exp'] : 'none'}\n\tcached secret: ${((_a = this.currentLongLivedSecret) === null || _a === void 0 ? void 0 : _a.value) != undefined}\n\tcached secret type: ${(_b = this.currentLongLivedSecret) === null || _b === void 0 ? void 0 : _b.type}`;
    }
    getCachedOrRefreshedOrNewToken() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!!this.cachedToken && !(0, JwtUtils_1.isJwtInvalidOrExpired)(this.cachedToken)) {
                return { token: this.cachedToken, type: RetrievedTokenType.CACHED };
            }
            else if (!!this.cachedRefreshToken && !(0, JwtUtils_1.isJwtInvalidOrExpired)(this.cachedRefreshToken)) {
                return this.refreshAndCacheToken(this.cachedRefreshToken);
            }
            else {
                return { token: yield this.getAndCacheNewToken(undefined), type: RetrievedTokenType.NEW };
            }
        });
    }
    getCachedTokensOrLoad() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.cachedToken || !this.cachedRefreshToken) {
                yield this.getAndCacheNewToken(undefined);
            }
            return { token: this.cachedToken, refreshToken: this.cachedRefreshToken };
        });
    }
    getNewTokenWithClass(minimumAuthenticationClass) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.getAndCacheNewToken(minimumAuthenticationClass);
        });
    }
    getAndCacheNewToken(minimumAuthenticationClassLevel) {
        return __awaiter(this, void 0, void 0, function* () {
            const { token, refreshToken } = yield this.getNewToken(minimumAuthenticationClassLevel !== null && minimumAuthenticationClassLevel !== void 0 ? minimumAuthenticationClassLevel : 0);
            this.cachedToken = token;
            this.cachedRefreshToken = refreshToken;
            return token;
        });
    }
    refreshAndCacheToken(refreshToken) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.debugLog)
                console.log(`Attempting to refresh bearer token ${this.debugCacheInfo()}`);
            return yield this.authApi.refreshAuthenticationJWT(refreshToken).then((authResult) => {
                if (!authResult.token)
                    throw new Error('Internal error: refresh succeeded but no token was returned. Unsupported backend version?');
                this.cachedToken = authResult.token;
                return { token: authResult.token, type: RetrievedTokenType.REFRESHED };
            }, () => __awaiter(this, void 0, void 0, function* () { return ({ token: yield this.getAndCacheNewToken(undefined), type: RetrievedTokenType.NEW }); }));
        });
    }
    getNewToken(minimumAuthenticationClassLevel) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.debugLog)
                console.log(`Attempting to get new token for auth class ${minimumAuthenticationClassLevel} ${this.debugCacheInfo()}`);
            if (!!this.currentLongLivedSecret && (!this.currentLongLivedSecret.type || this.currentLongLivedSecret.type >= minimumAuthenticationClassLevel)) {
                if (this.debugLog)
                    console.log('Using cached secret');
                const resultWithCachedSecret = yield this.doGetTokenWithSecret({ cached: this.currentLongLivedSecret }, minimumAuthenticationClassLevel);
                if ('success' in resultWithCachedSecret) {
                    if (this.debugLog)
                        console.log('New token using cached secret success');
                    return resultWithCachedSecret.success;
                }
                else if (resultWithCachedSecret.failure === DoGetTokenResultFailureReason.NEEDS_2FA &&
                    minimumAuthenticationClassLevel <= ServerAuthenticationClass.TWO_FACTOR_AUTHENTICATION) {
                    if (this.debugLog)
                        console.log('New token using cached secret requires 2fa');
                    return this.askTotpAndGetToken(this.currentLongLivedSecret.value, minimumAuthenticationClassLevel);
                }
                else {
                    if (this.debugLog)
                        console.log('New token using cached secret failed');
                    return this.askSecretAndGetToken(minimumAuthenticationClassLevel, true);
                }
            }
            else {
                if (this.debugLog)
                    console.log('No cached credentials of correct auth class found');
                return this.askSecretAndGetToken(minimumAuthenticationClassLevel, true);
            }
        });
    }
    askSecretAndGetToken(minimumAuthenticationClassLevel, passwordIsValidAs2fa) {
        return __awaiter(this, void 0, void 0, function* () {
            const acceptedSecrets = [
                minimumAuthenticationClassLevel <= ServerAuthenticationClass.LONG_LIVED_TOKEN && this.login != undefined
                    ? [AuthSecretType.LONG_LIVED_TOKEN]
                    : [],
                minimumAuthenticationClassLevel <= ServerAuthenticationClass.SHORT_LIVED_TOKEN && this.login != undefined
                    ? [AuthSecretType.SHORT_LIVED_TOKEN]
                    : [],
                minimumAuthenticationClassLevel <= ServerAuthenticationClass.TWO_FACTOR_AUTHENTICATION &&
                    this.login != undefined &&
                    (passwordIsValidAs2fa || minimumAuthenticationClassLevel <= ServerAuthenticationClass.PASSWORD)
                    ? [AuthSecretType.PASSWORD]
                    : [],
                minimumAuthenticationClassLevel <= 0 ? [AuthSecretType.ICURE_CLOUD] : [],
                minimumAuthenticationClassLevel <= ServerAuthenticationClass.DIGITAL_ID ? [AuthSecretType.DIGITAL_ID] : [],
                minimumAuthenticationClassLevel <= ServerAuthenticationClass.EXTERNAL_AUTHENTICATION ? [AuthSecretType.EXTERNAL_AUTHENTICATION] : [],
            ].flat();
            if (!acceptedSecrets.length)
                throw new Error('Internal error: no secret type is accepted for this request. Group may be misconfigured, or client may be outdated.');
            const attempts = [];
            if (this.debugLog)
                console.log(`Asking for secret and getting token for auth class ${minimumAuthenticationClassLevel} ${this.debugCacheInfo()}`);
            while (true) {
                const secretDetails = yield this.authSecretProvider.getSecret([...acceptedSecrets], attempts, minimumAuthenticationClassLevel > 0);
                if (!acceptedSecrets.includes(secretDetails.secretType))
                    throw new Error(`Accepted secret types are ${JSON.stringify(acceptedSecrets)}, but got a secret of type ${secretDetails.secretType}.`);
                attempts.push(secretDetails);
                const result = yield this.doGetTokenWithSecret({ new: secretDetails }, minimumAuthenticationClassLevel);
                if ('success' in result) {
                    this.updateCachedSecret(secretDetails);
                    return result.success;
                }
                else if (result.failure == DoGetTokenResultFailureReason.NEEDS_2FA) {
                    return this.askTotpAndGetToken(secretDetails.value, minimumAuthenticationClassLevel);
                }
                else if (secretDetails.value == AuthSecretType.PASSWORD && result.failure == DoGetTokenResultFailureReason.INVALID_AUTH_CLASS_LEVEL) {
                    // If we tried a password, and it turns out that the user has 2fa not enabled next time we don't consider password valid
                    return this.askSecretAndGetToken(minimumAuthenticationClassLevel, false);
                } // else retry
            }
        });
    }
    askTotpAndGetToken(password, minimumAuthenticationClassLevel) {
        return __awaiter(this, void 0, void 0, function* () {
            if (minimumAuthenticationClassLevel > ServerAuthenticationClass.TWO_FACTOR_AUTHENTICATION)
                throw new Error("Internal error: asking for totp to login but minimumAuthenticationClassLevel is higher than TWO_FACTOR_AUTHENTICATION's level.");
            const attempts = [];
            while (true) {
                const details = yield this.authSecretProvider.getSecret([AuthSecretType.TWO_FACTOR_AUTHENTICATION_TOKEN], attempts, false);
                if (details.secretType != AuthSecretType.TWO_FACTOR_AUTHENTICATION_TOKEN)
                    throw new Error(`Was expecting a 2fa token but got a secret of type ${details.secretType}.`);
                attempts.push(details);
                const result = yield this.doGetTokenWithSecret({ new: { value: `${password}|${details.value}`, secretType: AuthSecretType.TWO_FACTOR_AUTHENTICATION_TOKEN } }, minimumAuthenticationClassLevel);
                if ('success' in result) {
                    this.updateCachedSecret({ value: password, secretType: AuthSecretType.PASSWORD });
                    return result.success;
                }
                else if (result.failure != DoGetTokenResultFailureReason.INVALID_2FA) {
                    throw new Error(`Unexpected error while trying to login with (previously) valid password and 2fa token ${result.failure}.`);
                } // else retry
            }
        });
    }
    doGetTokenWithSecret(secretDetails, minimumAuthenticationClassLevel) {
        return __awaiter(this, void 0, void 0, function* () {
            let authResultPromise;
            if ('cached' in secretDetails) {
                const secret = secretDetails.cached;
                if ('oauthType' in secret && !!secret.oauthType) {
                    authResultPromise = this.authApi.loginWithThirdPartyToken(secret.oauthType, secret.value, this.groupId);
                }
                else {
                    authResultPromise = this.authApi.login({ username: this.login, password: secret.value }, this.groupId);
                }
            }
            else {
                const secret = secretDetails.new;
                if (secret.secretType == AuthSecretType.DIGITAL_ID) {
                    authResultPromise = this.authApi.loginWithThirdPartyToken(secret.provider, secret.value, this.groupId);
                }
                else if (secret.secretType == AuthSecretType.EXTERNAL_AUTHENTICATION) {
                    authResultPromise = this.authApi.loginWithThirdPartyToken(secret.oauthType, secret.value, this.groupId);
                }
                else if (secret.secretType == AuthSecretType.ICURE_CLOUD) {
                    authResultPromise = this.authApi.loginWithCloudToken(secret.issuer, secret.value);
                }
                else {
                    authResultPromise = this.authApi.login({ username: this.login, password: secret.value }, this.groupId);
                }
            }
            return authResultPromise.then((authResult) => {
                const { token, refreshToken } = authResult;
                if (!token || !refreshToken)
                    throw new Error('Internal error: login succeeded but no token was returned. Unsupported backend version?');
                const claims = (0, JwtUtils_1.decodeJwtClaims)(token);
                const authClassLevel = claims['tac'];
                if (!authClassLevel || typeof authClassLevel !== 'number') {
                    if (minimumAuthenticationClassLevel > 0) {
                        throw new Error('Internal error: authClassLevel is not a number. Unsupported backend version?');
                    }
                    else
                        return { success: { token, refreshToken } };
                }
                if (authClassLevel < minimumAuthenticationClassLevel) {
                    return { failure: DoGetTokenResultFailureReason.INVALID_AUTH_CLASS_LEVEL };
                }
                else {
                    return { success: { token, refreshToken } };
                }
            }, (error) => {
                if (!(error instanceof XHRError))
                    throw error;
                if (error.statusCode == 401 || error.statusCode == 412) {
                    // Password is wrong (401) or unacceptable (e.g. too short, 412)
                    return { failure: DoGetTokenResultFailureReason.INVALID_PW_OR_TOKEN };
                }
                else if (error.statusCode == 406) {
                    // Password is correct, but 2fa token is not
                    return { failure: DoGetTokenResultFailureReason.INVALID_2FA };
                }
                else if (error.statusCode == 417) {
                    // Password is correct, but the user has 2fa enabled and no 2fa token was provided
                    return { failure: DoGetTokenResultFailureReason.NEEDS_2FA };
                }
                else
                    throw error;
            });
        });
    }
    switchedGroup(newGroupId) {
        return __awaiter(this, void 0, void 0, function* () {
            const groupSwitchedTokens = this.cachedRefreshToken
                ? yield this.authApi.switchGroup(this.cachedRefreshToken, newGroupId).then((response) => {
                    if (!response.token || !response.refreshToken)
                        throw new Error('Internal error: group switch succeeded but no token was returned. Unsupported backend version?');
                    return { token: response.token, refreshToken: response.refreshToken };
                }, () => ({ token: undefined, refreshToken: undefined }))
                : { token: undefined, refreshToken: undefined };
            return new TokenProvider(this.login, newGroupId, this.currentLongLivedSecret ? { value: this.currentLongLivedSecret.value, type: undefined } : undefined, groupSwitchedTokens.token, groupSwitchedTokens.refreshToken, this.authApi, this.authSecretProvider, this.debugLog);
        });
    }
    updateCachedSecret(details) {
        switch (details.secretType) {
            case AuthSecretType.PASSWORD:
                this.currentLongLivedSecret = { value: details.value, type: ServerAuthenticationClass.PASSWORD };
                break;
            case AuthSecretType.LONG_LIVED_TOKEN:
                this.currentLongLivedSecret = { value: details.value, type: ServerAuthenticationClass.LONG_LIVED_TOKEN };
                break;
            case AuthSecretType.EXTERNAL_AUTHENTICATION:
                if (longLivedOAuthTokens.has(details.oauthType)) {
                    this.currentLongLivedSecret = {
                        value: details.value,
                        type: ServerAuthenticationClass.EXTERNAL_AUTHENTICATION,
                        oauthType: details.oauthType,
                    };
                }
                break;
        }
    }
}
var DoGetTokenResultFailureReason;
(function (DoGetTokenResultFailureReason) {
    DoGetTokenResultFailureReason[DoGetTokenResultFailureReason["NEEDS_2FA"] = 0] = "NEEDS_2FA";
    DoGetTokenResultFailureReason[DoGetTokenResultFailureReason["INVALID_2FA"] = 1] = "INVALID_2FA";
    DoGetTokenResultFailureReason[DoGetTokenResultFailureReason["INVALID_PW_OR_TOKEN"] = 2] = "INVALID_PW_OR_TOKEN";
    DoGetTokenResultFailureReason[DoGetTokenResultFailureReason["INVALID_AUTH_CLASS_LEVEL"] = 3] = "INVALID_AUTH_CLASS_LEVEL";
})(DoGetTokenResultFailureReason || (DoGetTokenResultFailureReason = {}));
var RetrievedTokenType;
(function (RetrievedTokenType) {
    RetrievedTokenType[RetrievedTokenType["CACHED"] = 0] = "CACHED";
    RetrievedTokenType[RetrievedTokenType["REFRESHED"] = 1] = "REFRESHED";
    RetrievedTokenType[RetrievedTokenType["NEW"] = 2] = "NEW";
})(RetrievedTokenType || (RetrievedTokenType = {}));
var SmartAuthServiceState;
(function (SmartAuthServiceState) {
    SmartAuthServiceState[SmartAuthServiceState["INITIAL"] = 0] = "INITIAL";
    SmartAuthServiceState[SmartAuthServiceState["DONE_INITIAL"] = 1] = "DONE_INITIAL";
    SmartAuthServiceState[SmartAuthServiceState["REATTEMPT"] = 2] = "REATTEMPT";
    SmartAuthServiceState[SmartAuthServiceState["REATTEMPTED_WITH_NEW_UNBOUND_TOKEN"] = 3] = "REATTEMPTED_WITH_NEW_UNBOUND_TOKEN";
    SmartAuthServiceState[SmartAuthServiceState["REATTEMPTED_WITH_AUTH_CLASS_SPECIFIC_TOKEN"] = 4] = "REATTEMPTED_WITH_AUTH_CLASS_SPECIFIC_TOKEN";
    SmartAuthServiceState[SmartAuthServiceState["EXPECT_REQUEST_WITH_SPECIFIC_AUTH_CLASS"] = 5] = "EXPECT_REQUEST_WITH_SPECIFIC_AUTH_CLASS";
    SmartAuthServiceState[SmartAuthServiceState["TERMINAL_ERROR"] = 6] = "TERMINAL_ERROR";
})(SmartAuthServiceState || (SmartAuthServiceState = {}));
class SmartAuthService {
    constructor(tokenProvider) {
        this.tokenProvider = tokenProvider;
        this.currentState = { id: SmartAuthServiceState.INITIAL };
    }
    jwtGetter() {
        return __awaiter(this, void 0, void 0, function* () {
            const token = yield this.getAuthToken(undefined);
            return { token, refreshToken: undefined };
        });
    }
    getAuthHeaders(minimumAuthenticationClassLevel) {
        return __awaiter(this, void 0, void 0, function* () {
            return [new XHR_1.XHR.Header('Authorization', `Bearer ${yield this.getAuthToken(minimumAuthenticationClassLevel)}`)];
        });
    }
    getAuthToken(minimumAuthenticationClassLevel) {
        return __awaiter(this, void 0, void 0, function* () {
            switch (this.currentState.id) {
                case SmartAuthServiceState.INITIAL:
                    if (minimumAuthenticationClassLevel != undefined) {
                        throw new Error('Illegal state: cannot ask for a specific auth class level at the first request attempt.');
                    }
                    else {
                        const { token } = yield this.tokenProvider.getCachedOrRefreshedOrNewToken();
                        this.currentState = { id: SmartAuthServiceState.DONE_INITIAL, initialToken: token };
                        return token;
                    }
                case SmartAuthServiceState.REATTEMPT:
                    if (minimumAuthenticationClassLevel != undefined) {
                        const token = yield this.tokenProvider.getNewTokenWithClass(minimumAuthenticationClassLevel);
                        this.currentState = { id: SmartAuthServiceState.REATTEMPTED_WITH_AUTH_CLASS_SPECIFIC_TOKEN };
                        return token;
                    }
                    else {
                        const { token } = yield this.tokenProvider.getCachedOrRefreshedOrNewToken();
                        if (token == this.currentState.initialToken)
                            throw this.currentState.initialError;
                        this.currentState = { id: SmartAuthServiceState.REATTEMPTED_WITH_NEW_UNBOUND_TOKEN };
                        return token;
                    }
                case SmartAuthServiceState.EXPECT_REQUEST_WITH_SPECIFIC_AUTH_CLASS:
                    if (minimumAuthenticationClassLevel != undefined) {
                        const token = yield this.tokenProvider.getNewTokenWithClass(minimumAuthenticationClassLevel);
                        this.currentState = { id: SmartAuthServiceState.REATTEMPTED_WITH_AUTH_CLASS_SPECIFIC_TOKEN };
                        return token;
                    }
                    else
                        throw this.currentState.errorFromNewToken;
                case SmartAuthServiceState.TERMINAL_ERROR:
                    throw this.currentState.error;
                default:
                    throw new Error(`Illegal state: cannot get token in state ${this.currentState.id}.`);
            }
        });
    }
    invalidateHeader(error) {
        switch (this.currentState.id) {
            case SmartAuthServiceState.DONE_INITIAL:
                this.currentState = { id: SmartAuthServiceState.REATTEMPT, initialToken: this.currentState.initialToken, initialError: error };
                break;
            case SmartAuthServiceState.REATTEMPTED_WITH_NEW_UNBOUND_TOKEN:
                this.currentState = { id: SmartAuthServiceState.EXPECT_REQUEST_WITH_SPECIFIC_AUTH_CLASS, errorFromNewToken: error };
                break;
            case SmartAuthServiceState.REATTEMPTED_WITH_AUTH_CLASS_SPECIFIC_TOKEN:
                this.currentState = { id: SmartAuthServiceState.TERMINAL_ERROR, error: error };
                break;
            default:
                throw new Error(`Illegal state: cannot invalidate header in state ${this.currentState.id}.`);
        }
    }
}
//# sourceMappingURL=SmartAuthProvider.js.map