@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
632 lines • 83.7 kB
JavaScript
import { Injectable, Injector, Optional } from '@angular/core';
import { BasicAuth, BearerAuthFromSessionStorage, CookieAuth, FetchClient, Realtime, TenantLoginOptionsService, TenantService, UserService } from '@c8y/client';
import { AppStateService } from '../common/ui-state.service';
import { AlertService } from '../alert/alert.service';
import { gettext } from '../i18n/gettext';
import { ApiService } from '@c8y/ngx-components/api';
import { TenantUiService } from '../common/tenant-ui.service';
import { switchMap } from 'rxjs/operators';
import { BehaviorSubject, EMPTY } from 'rxjs';
import { LocationStrategy } from '@angular/common';
import { get, isEmpty, isString } from 'lodash-es';
import { TranslateService } from '@ngx-translate/core';
import { ModalService } from '../modal/modal.service';
import { Status } from '../common/status.model';
import * as i0 from "@angular/core";
import * as i1 from "@c8y/client";
import * as i2 from "../common/ui-state.service";
import * as i3 from "../alert/alert.service";
import * as i4 from "@c8y/ngx-components/api";
import * as i5 from "../common/tenant-ui.service";
import * as i6 from "@ngx-translate/core";
import * as i7 from "../modal/modal.service";
import * as i8 from "@angular/common";
/**
* Service to manage the login.
*/
export class LoginService {
constructor(injector, client, basicAuth, cookieAuth, ui, user, tenant, realtime, alert, api, tenantUiService, tenantLoginOptionsService, translateService, modalService, location) {
this.injector = injector;
this.client = client;
this.basicAuth = basicAuth;
this.cookieAuth = cookieAuth;
this.ui = ui;
this.user = user;
this.tenant = tenant;
this.realtime = realtime;
this.alert = alert;
this.api = api;
this.tenantUiService = tenantUiService;
this.tenantLoginOptionsService = tenantLoginOptionsService;
this.translateService = translateService;
this.modalService = modalService;
this.location = location;
this.rememberMe = false;
this.TOKEN_KEY = '_tcy8';
this.TFATOKEN_KEY = 'TFAToken';
this.isFirstLogin = true;
this.GREEN_MIN_LENGTH_DEFAULT = 8;
this.automaticLoginInProgress$ = new BehaviorSubject(false);
// tslint:disable:max-line-length
this.ERROR_MESSAGES = {
minlength: gettext('Password must have at least 8 characters and no more than 32.'),
password_missmatch: gettext('Passwords do not match.'),
maxlength: gettext('Password must have at least 8 characters and no more than 32.'),
password_strength: gettext('Your password is not strong enough. Please include numbers, lower and upper case characters'),
remote_error: gettext('Server error occurred.'),
email: gettext('Invalid email address.'),
password_change: gettext('Your password is expired. Please set a new password.'),
password_reset_token_expired: gettext('Password reset link expired. Please enter your email address to receive a new one.'),
tfa_pin_invalid: gettext('The code you entered is invalid. Please try again.'),
pattern_newPassword: this.translateService.instant(gettext('Password must have at least 8 characters and no more than 32 and can only contain letters, numbers and following symbols: {{ symbols }}'), { symbols: '`~!@#$%^&*()_|+-=?;:\'",.<>{}[]\\/' }),
internationalPhoneNumber: gettext('Must be a valid phone number (only digits, spaces, slashes ("/"), dashes ("-"), and plus ("+") allowed, for example: +49 9 876 543 210).'),
phone_number_error: gettext('Could not update phone number.'),
pinAlreadySent: gettext('The verification code was already sent. For a new verification code, please click on the link above.'),
passwordConfirm: gettext('Passwords do not match.'),
tfaExpired: gettext('Two-factor authentication token expired.')
};
// tslint:enable:max-line-length
this.SUCCESS_MESSAGES = {
password_changed: gettext('Password changed. You can now log in using new password.'),
password_reset_requested: gettext('Password reset request has been sent. Please check your email.'),
resend_sms: gettext('Verification code SMS resent.'),
send_sms: gettext('Verification code SMS sent.')
};
this.passwordStrengthSetting = {
enforcePasswordStrength: false,
greenMinLength: this.GREEN_MIN_LENGTH_DEFAULT,
passwordStrengthValidity: false
};
this.localhostRegExp = new RegExp('localhost');
this.localhostIpRegExp = new RegExp('127.0.0.1');
this.showTenantRegExp = new RegExp('showTenant');
this.autoLogout();
this.initLoginOptions();
}
/**
* Returns the current tenant.
* @return The tenant name.
*/
getTenant() {
return this.client.tenant;
}
initLoginOptions() {
const loginOptions = this.ui.state.loginOptions || [];
this.loginMode = this.tenantUiService.getPreferredLoginOption(loginOptions);
this.oauthOptions =
this.tenantUiService.getOauth2Option(loginOptions) || {};
}
redirectToOauth() {
const { initRequest, flowControlledByUI } = this.oauthOptions;
const fullPath = `${window.location.origin}${window.location.pathname}`;
const redirectUrl = encodeURIComponent(fullPath);
const originUriParam = `${initRequest.includes('?') ? '&' : '?'}originUri=${redirectUrl}`;
const urlObject = new URL(initRequest);
if (flowControlledByUI) {
this.client
.fetch(`/tenant/oauth${urlObject.search}${originUriParam}`)
.then(res => this.handleErrorStatusCodes(res))
.then(res => res.json())
.then((res) => (window.location.href = res.redirectTo))
.catch(ex => this.showSsoError(ex));
}
else {
window.location.href = `${initRequest}${originUriParam}`;
}
}
loginBySso({ code, sessionState }) {
const params = {
method: 'GET',
headers: {
Accept: 'text/html,application/xhtml+xml'
}
};
let url = `/tenant/oauth?code=${encodeURIComponent(code)}`;
if (sessionState) {
url += `&session_state=${encodeURIComponent(sessionState)}`;
}
return this.client
.fetch(url, params)
.then(res => this.handleErrorStatusCodes(res))
.catch(ex => {
this.showSsoError(ex);
throw new Error();
});
}
autoLogout() {
const errorPattern = /invalid\scredentials.*pin.*generate/i;
const isTfaExpired = data => data && typeof data.message === 'string' && errorPattern.test(data.message);
this.ui.currentUser
.pipe(switchMap(u => u ? this.api.hookResponse(({ response }) => response.status === 401) : EMPTY))
.subscribe(async (apiCall) => {
const { response } = apiCall;
let willLogout = false;
if (isTfaExpired(response.data)) {
willLogout = true;
}
else {
if (typeof response.json === 'function') {
const data = await response.clone().json();
if (isTfaExpired(data)) {
willLogout = true;
}
}
}
if (willLogout) {
this.logout(false);
setTimeout(() => this.alert.danger(this.ERROR_MESSAGES.tfaExpired), 500);
}
});
}
/**
* Gets the minimal number of characters that a password should have to be considered a “green” strong one.
* @return The min length for password or default value.
*/
async getGreenMinLength() {
const { greenMinLength } = (await this.getBasicAuthLoginOption()) || { greenMinLength: null };
this.passwordStrengthSetting.greenMinLength = greenMinLength || this.GREEN_MIN_LENGTH_DEFAULT;
return this.passwordStrengthSetting.greenMinLength;
}
/**
* Checks if password strength is enforced for system
* by retrieving value of `enforceStrength` property from loginOptions response
* @param refresh boolean used to refresh the app state where result of loginOptions response is stored.
* If false, it takes value from memory,
* if true, it refresh the app state value and then retrives data.
* @return boolean value, true if enforced, false otherwise.
*/
async getEnforcePasswordStrength(refresh) {
return this.getBasicAuthLoginOption(refresh).then(loginOption => {
const enforcePasswordStrength = get(loginOption, 'enforceStrength');
if (isString(enforcePasswordStrength)) {
this.passwordStrengthSetting.enforcePasswordStrength =
enforcePasswordStrength === 'true' ? true : false;
}
else {
this.passwordStrengthSetting.enforcePasswordStrength = !!enforcePasswordStrength;
}
return this.passwordStrengthSetting.enforcePasswordStrength;
});
}
/**
* Checks if password strength is enforced for particular tenant
* by retrieving value of `strengthValidity` property from loginOptions response
* @param refresh boolean used to refresh the app state where result of loginOptions response is stored.
* If false, it takes value from memory,
* if true, it refresh the app state value and then retrives data.
* @return boolean value, true if enforced, false otherwise.
*/
async getPasswordStrengthValidity(refresh) {
return this.getBasicAuthLoginOption(refresh).then(loginOption => {
const strengthValidity = get(loginOption, 'strengthValidity');
if (isString(strengthValidity)) {
this.passwordStrengthSetting.passwordStrengthValidity =
strengthValidity === 'true' ? true : false;
}
else {
this.passwordStrengthSetting.passwordStrengthValidity = !!strengthValidity;
}
return this.passwordStrengthSetting.passwordStrengthValidity;
});
}
/**
* Function determines if enforced strength checks should be enabled for current tenant
* based on properties retrieved from loginOptions
* @param options object containing specific options:
* - {refresh: true} - refreshes values of app state and returns fresh values as result of call
* @return boolean value, true if strength is enforced for tenant, false otherwise.
*/
async getPasswordStrengthEnforced(options) {
const refresh = options && options.refresh;
return Promise.all([
this.getEnforcePasswordStrength(refresh),
this.getPasswordStrengthValidity(refresh)
]).then(values => {
const [enforcePasswordStrength, passwordStrengthValidity] = values;
return enforcePasswordStrength || passwordStrengthValidity;
});
}
/**
* Clears all backend errors.
*/
cleanMessages() {
this.alert.clearAll();
}
/**
* Adds a new success message
* @param successKey The key of the success message as used in SUCCESS_MESSAGES
*/
addSuccessMessage(successKey) {
const successMessage = this.SUCCESS_MESSAGES[successKey];
if (successMessage) {
this.alert.add({
text: successMessage,
type: 'success',
timeout: 0
});
}
}
/**
* Returns the current strategy. Defaults to cookie, if a token
* is found in local or session storage we switch to basic auth.
* @returns The current auth strategy.
*/
getAuthStrategy() {
try {
const authStrategy = new BearerAuthFromSessionStorage();
console.log(`Using BearerAuthFromSessionStorage`);
return authStrategy;
}
catch (e) {
// do nothing
}
let authStrategy = this.cookieAuth;
const token = this.getStoredToken();
const tfa = this.getStoredTfaToken();
if (token) {
authStrategy = this.basicAuth;
this.setCredentials({ token, tfa }, this.basicAuth);
}
return authStrategy;
}
/**
* Forces the use of basic auth as strategy with this credentials.
* @param credentials The credentials to use.
*/
useBasicAuth(credentials) {
this.setCredentials(credentials, this.basicAuth);
return this.basicAuth;
}
/**
* Tries to login a user with the given credentials.
* If successful, the current tenant and user is set. If not an error
* is thrown. It also verifies if the user is allowed to open the
* current app.
* @param auth The authentication strategy used.
* @param credentials The credentials to try to login.
*/
async login(auth = this.getAuthStrategy(), credentials) {
// To ensure backward compatibility, we need to verify whether the backend supports TFA
// without requiring the use of /tenant with auth: base64. The tfaSupported flag indicates
// whether authentication is possible exclusively via OAI-SECURE.
// TfaSupported flag should be removed during: MTM-62641
const isOAISecureAndTFAIsSupported = (this.tenantUiService.isOauthInternal(this.loginMode) && this.loginMode.tfaSupported) ||
false;
if (isOAISecureAndTFAIsSupported && (await this.switchLoginMode(credentials))) {
auth = this.cookieAuth;
}
else {
this.client.setAuth(auth);
}
const tenantRes = await this.tenant.current({ withParent: true });
const tenant = tenantRes.data;
if (credentials) {
credentials.tenant = tenant.name;
}
if (!isOAISecureAndTFAIsSupported && (await this.switchLoginMode(credentials))) {
auth = this.cookieAuth;
}
const userRes = await this.user.current();
const user = userRes.data;
await this.verifyAppAccess();
const supportUserName = this.getSupportUserName(credentials);
const token = this.setCredentials({
tenant: tenant.name,
user: (supportUserName ? `${supportUserName}$` : '') + user.userName
}, auth);
if (token) {
this.storeBasicAuthToken(token);
}
await this.authFulfilled(tenant, user, supportUserName);
}
/**
* Saves tenant, user and support user info to the app state.
* @param tenant The current tenant object.
* @param user The current user object.
* @param supportUserName The current support user name.
*/
async authFulfilled(tenant, user, supportUserName) {
if (!tenant) {
const { data } = await this.tenant.current({ withParent: true });
tenant = data;
this.client.tenant = tenant.name;
}
if (!user) {
const { data } = await this.user.current();
user = data;
}
if (!supportUserName) {
supportUserName = null;
}
this.ui.setUser({ user, supportUserName });
this.ui.currentTenant.next(tenant);
}
/**
* Switch the login mode to CookieAuth if the
* user has configured to use it in loginOptions.
* @param credentials The credentials for that login
*/
async switchLoginMode(credentials) {
const isPasswordGrantLogin = await this.isPasswordGrantLogin(credentials);
if (isPasswordGrantLogin && credentials) {
const res = await this.generateOauthToken(credentials);
if (!res.ok) {
try {
const data = await res.json();
throw { res, data };
}
catch (ex) {
throw ex;
}
}
this.client.setAuth(this.cookieAuth);
this.cleanLocalStorage();
this.basicAuth.logout();
}
return isPasswordGrantLogin;
}
async generateOauthToken(credentials) {
if ((await this.isPasswordGrantLogin(credentials)) && credentials) {
const params = new URLSearchParams({
grant_type: 'PASSWORD',
username: credentials.user,
password: credentials.password,
...(credentials.tfa !== undefined && { tfa_code: credentials.tfa })
});
return await new FetchClient().fetch(this.getUrlForOauth(credentials), {
method: 'POST',
body: params.toString(),
headers: {
'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
});
}
}
async isPasswordGrantLogin(credentials) {
let loginMode = this.loginMode;
if (this.isSupportUser(credentials)) {
if (!this.managementLoginMode) {
this.managementLoginMode = await this.getManagementLoginMode();
}
loginMode = this.managementLoginMode;
}
return this.tenantUiService.isOauthInternal(loginMode);
}
/**
* Verifies if the provided credentials use a support user to log in or not.
* @param credentials Credentials to check.
* @returns {boolean} Returns true if user is a support user.
*/
isSupportUser(credentials) {
return credentials && credentials.user.includes('$');
}
/**
* Verifies if the tenant input field should be shown
* or not.
* @returns If true, show the tenant input.
*/
showTenant() {
return !this.ui.state.loginOptions || this.isLocal() || this.isShowTenant();
}
/**
* Verifies if the tenant setup should be shown
* or not.
* @returns If true, show the tenant input.
*/
showTenantSetup() {
return !this.ui.state.loginOptions && !this.isLocal();
}
/**
* Logs the user out
* @param reload If set to false, the page will not reload
*/
async logout(reload = true) {
let resData = null;
try {
const [, cookieRes] = await this.reset();
resData = await cookieRes.json();
}
catch (ex) {
this.alert.removeLastDanger();
}
finally {
if (resData && resData.url) {
this.redirect(resData.url);
}
else if (reload) {
this.location.replaceState({}, '', '', '');
window.location.reload();
}
}
}
/**
* Resets the stored auth-data
*/
async reset() {
this.cleanLocalStorage();
this.cleanSessionStorage();
this.realtime.disconnect();
this.ui.currentUser.next(null);
return Promise.all([this.basicAuth.logout(), this.cookieAuth.logout()]);
}
/**
* Saves the TFA token to local or session storage.
* @param tfaToken The tfa token to save.
* @param storage The storage to use (local or session).
*/
saveTFAToken(tfaToken, storage) {
storage.setItem(this.TFATOKEN_KEY, tfaToken);
}
/**
* Request the manifest -> on 401 user has no access to that application
* and we throw the error up to the login form.
*/
async verifyAppAccess() {
try {
await this.ui.loadManifest();
}
catch (ex) {
if (!(ex.res && ex.res.status === 404 && this.isLocal())) {
throw ex;
}
}
}
redirectToDomain(domain) {
const originUrl = new URL(window.location.href);
const redirectUrl = originUrl.href.replace(originUrl.hostname, domain);
window.location.href = redirectUrl;
}
showSsoError(error) {
const body = error
? this.translateService.instant(gettext('<p><strong>The following error was returned from the external authentication service:</strong></p><p><code>{{ error }}</code></p>.'), { error })
: gettext('SSO login failed. Contact the administrator.');
this.modalService.acknowledge(gettext('Login error'), body, Status.DANGER, gettext('OK'));
}
/**
* Sets the tenant to the client and updates the credentials on the
* auth strategy.
* @param credentials The name of the tenant.
* @param authStrategy The authentication strategy used.
* @return Returns the token if basic auth, otherwise undefined.
*/
setCredentials(credentials, authStrategy) {
if (credentials.tenant) {
this.client.tenant = credentials.tenant;
}
// Check if a token is already set (case for support user login)
// if yes -> we just need to update the user, and reuse the token
// of the support user.
// Therefore we need to pass user and tenant, to get
// just the stored token and nothing else (see BasicAuth.ts:31).
const token = this.basicAuth.updateCredentials({
tenant: credentials.tenant,
user: credentials.user
});
const newCredentials = { token, ...credentials };
return authStrategy.updateCredentials(newCredentials);
}
/**
* Verifies if the current user is a developer or not.
* Running on localhost means development mode.
*/
isLocal() {
const hostname = window.location.hostname;
return this.localhostIpRegExp.test(hostname) || this.localhostRegExp.test(hostname);
}
/**
* Save the token to local or session storage.
* @param token The token to save.
* @param storage The storage to use (local or session).
*/
saveToken(token, storage) {
storage.setItem(this.TOKEN_KEY, token);
}
storeBasicAuthToken(token) {
this.saveToken(token, sessionStorage);
if (this.rememberMe) {
this.saveToken(token, localStorage);
}
}
cleanLocalStorage() {
localStorage.removeItem(this.TOKEN_KEY);
localStorage.removeItem(this.TFATOKEN_KEY);
}
cleanSessionStorage() {
sessionStorage.removeItem(this.TOKEN_KEY);
sessionStorage.removeItem(this.TFATOKEN_KEY);
}
isShowTenant() {
return this.showTenantRegExp.test(window.location.href);
}
redirect(url) {
window.location.href = url;
}
async getBasicAuthLoginOption(refresh) {
if (refresh) {
await this.ui.refreshLoginOptions();
}
const loginOptions = this.ui.state.loginOptions || [];
const basicAuthLoginOption = loginOptions.find(({ type }) => type === 'BASIC');
return Promise.resolve(basicAuthLoginOption);
}
/**
* Gets support user name from credentials.
* @param credentials Credentials object (defaults to the stored one).
* @returns Support user name.
*/
getSupportUserName(credentials = this.getStoredCredentials()) {
if (!credentials) {
return null;
}
const supportUserName = credentials.user.match(/^(.+\/)?((.+)\$)?(.+)?$/)[3];
return supportUserName;
}
/**
* Gets credentials object from the stored token.
* @returns Credentials object.
*/
getStoredCredentials() {
const token = this.getStoredToken();
if (!token) {
return null;
}
return this.decodeToken(token);
}
/**
* Gets stored token from local storage or session storage.
* @returns Stored token.
*/
getStoredToken() {
return localStorage.getItem(this.TOKEN_KEY) || sessionStorage.getItem(this.TOKEN_KEY);
}
/**
* Gets stored TFA token from local storage or session storage.
* @returns Stored TFA token.
*/
getStoredTfaToken() {
return localStorage.getItem(this.TFATOKEN_KEY) || sessionStorage.getItem(this.TFATOKEN_KEY);
}
/**
* Decodes token to credentials object.
* @param token Token to decode.
* @returns Credentials object.
*/
decodeToken(token) {
const decoded = decodeURIComponent(escape(window.atob(token)));
const split = decoded.match(/(([^/]*)\/)?([^/:]+):(.+)/);
return {
tenant: split[2],
user: split[3],
password: split[4]
};
}
getUrlForOauth(credentials) {
if (isEmpty(credentials.tenant) && this.loginMode.initRequest) {
const urlParams = new URLSearchParams(this.loginMode.initRequest.split('?').pop());
credentials.tenant = urlParams.get('tenant_id');
}
return !isEmpty(credentials.tenant)
? `tenant/oauth?tenant_id=${credentials.tenant}`
: `tenant/oauth`;
}
async getManagementLoginMode() {
const managementLoginOptions = (await this.tenantLoginOptionsService.listForManagement()).data;
return this.tenantUiService.getPreferredLoginOption(managementLoginOptions);
}
async handleErrorStatusCodes(response) {
if (response.status >= 400) {
const data = await response.json();
const error = data.message || data.error_description || data.error;
throw error;
}
return response;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LoginService, deps: [{ token: i0.Injector }, { token: i1.FetchClient }, { token: i1.BasicAuth }, { token: i1.CookieAuth }, { token: i2.AppStateService }, { token: i1.UserService }, { token: i1.TenantService }, { token: i1.Realtime }, { token: i3.AlertService }, { token: i4.ApiService }, { token: i5.TenantUiService }, { token: i1.TenantLoginOptionsService }, { token: i6.TranslateService }, { token: i7.ModalService }, { token: i8.LocationStrategy, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LoginService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LoginService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i0.Injector }, { type: i1.FetchClient }, { type: i1.BasicAuth }, { type: i1.CookieAuth }, { type: i2.AppStateService }, { type: i1.UserService }, { type: i1.TenantService }, { type: i1.Realtime }, { type: i3.AlertService }, { type: i4.ApiService }, { type: i5.TenantUiService }, { type: i1.TenantLoginOptionsService }, { type: i6.TranslateService }, { type: i7.ModalService }, { type: i8.LocationStrategy, decorators: [{
type: Optional
}] }] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9naW4uc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2NvcmUvbG9naW4vbG9naW4uc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDL0QsT0FBTyxFQUNMLFNBQVMsRUFDVCw0QkFBNEIsRUFDNUIsVUFBVSxFQUNWLFdBQVcsRUFLWCxRQUFRLEVBQ1IseUJBQXlCLEVBQ3pCLGFBQWEsRUFDYixXQUFXLEVBQ1osTUFBTSxhQUFhLENBQUM7QUFDckIsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQzdELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3JELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDM0MsT0FBTyxFQUFFLGVBQWUsRUFBRSxLQUFLLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDOUMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDbkQsT0FBTyxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ25ELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7Ozs7Ozs7Ozs7QUFHaEQ7O0dBRUc7QUFFSCxNQUFNLE9BQU8sWUFBWTtJQStE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