// The MIT License (MIT) // // Copyright (c) 2021-2025 Camptocamp SA // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import {BehaviorSubject} from 'rxjs'; export interface AuthenticationFunctionalities { /** * Base maps to use by default. */ default_basemap: string[]; /** * Theme to use by default. */ default_theme: string[]; /** * A list of layer names that can be filtered. */ filterable_layers?: string[]; /** * When set, contains the name of the panel to open upon loading an application. */ open_panel?: string[]; /** * Default filtrable datasource name. */ preset_layer_filter?: string[]; } export interface RoleInfo { /** * Role identifier. */ id: number; /** * Role name. */ name: string; } export interface User { /** * User's email address. */ email: string; /** * The user is in the intranet. */ is_intranet: boolean; /** * Configured functionalities of the user. */ functionalities: AuthenticationFunctionalities; /** * True if the password of the user has been changed. False otherwise. */ is_password_changed: boolean; /** * Roles information. */ roles: RoleInfo[]; /** * The name of the user. */ username: string; /** * The user name to display in the UI. */ display_name: string; /** * The one-time-password Key */ otp_key: string; /** * The one-time-password URI */ otp_uri: string; /** * The two-factor authentication secret on first login */ two_factor_totp_secret: string; /** * The server-side login type (oidc or local) */ login_type?: string; } export enum UserState { /* eslint-disable no-unused-vars */ LOGGED_IN = 'logged in', LOGGED_OUT = 'logged out', DISCONNECTED = 'disconnected', READY = 'ready', NOT_INITIALIZED = 'not initialized', } // The name should be t to be collected by i18next function t(msg: string): string { return msg; } export const loginMessageRequired = t( 'Some layers in this link are not accessible to unauthenticated users. ' + 'Please log in to see whole data.', ); /** * Object used to expose the login user information. * * Example of usage: * ```js * (window as any).gmfapi.store.user.getProperties().subscribe({ * next: (user: User) => { * ... * }, * }) * ``` */ export class UserModel { /** * The observable user's properties. The default user is empty. * * @private */ properties_: BehaviorSubject; /** * The current state of the user. Default to NOT_INITIALIZED. * * @private */ state_: UserState; /** * The login message when a private layer is opened in the permalink * * @private */ loginMessage_: BehaviorSubject; constructor() { this.properties_ = new BehaviorSubject(this.getEmptyUserProperties()); this.loginMessage_ = new BehaviorSubject(''); this.state_ = UserState.NOT_INITIALIZED; } /** * @returns the observable user's properties. */ getProperties(): BehaviorSubject { return this.properties_; } /** * @returns the current user state. */ getState(): UserState { return this.state_; } /** * @returns The observable login message. */ getLoginMessage(): BehaviorSubject { return this.loginMessage_; } /** * Set the current login message * * @param messageState The new login message. */ setLoginMessage(messageState: string): void { this.loginMessage_.next(messageState); } /** * Set the current User's properties and state. * * @param properties The new user * @param state The new state */ setUser(properties: User, state: UserState): void { const isValid = this.checkUserProperties_(properties); if (!isValid || state === null) { return; } this.state_ = state; this.properties_.next(properties); } /** * @returns an empty user. */ getEmptyUserProperties(): User { return { ...{ email: null, is_intranet: null, functionalities: null, is_password_changed: null, roles: null, username: null, display_name: null, otp_key: null, otp_uri: null, two_factor_totp_secret: null, }, }; } /** * Check if the user has at least all required properties. * * @param properties The new user * @returns true if the properties are correct. * @private */ checkUserProperties_(properties: User): boolean { if (properties === null || properties === undefined) { console.error('New properties of the user must be an object'); return false; } let isValid = true; const keys = Object.keys(this.getEmptyUserProperties()); keys.forEach((key) => { const newKeys = Object.keys(properties); if (!newKeys.includes(key)) { console.error(`User is missing property ${key}`); isValid = false; } }); return isValid; } } // Export default user instantiated (to use it as a singleton). const user = new UserModel(); export default user;