UNPKG

@villedemontreal/jwt-validator

Version:
546 lines (545 loc) 21.8 kB
/************************************************************************************************ * User attributes * --------------- * All user attributes extend the CommonUserAttributes type, which defines a couple of common but optional attributes. * All user attributes have at least a username or an email to qualify. * * We have identified the following types of user: * - CitizenAttributes * - EmployeeAttributes * - ExternalUserAttributes * - GenericUserAttributes * - GuestUserAttributes * * If the subtype of user is not recognized, it will default to UnknownUserAttributes. * * The UserAttributes type is a union of all those types. It provides access to the * common optional attributes through the CommonUserAttributes type, * but it is better to test the 'type' of the attributes to access the strongly types attributes * with additional guarantees. * * Since some users might have multiple profiles and thus multiple emails (like @montreal.ca, .adm@montreal.ca or @spvm.qc.ca) * that they can select at login time, you might want to consider the "accountProfile" attribute which categorizes them. * The ID will correctly identify the physical person but not the selected role. * So, you could combine ID+accountProfile or prefer the email in some cases, like when you need to evaluate the permissions. * ************************************************************************************************/ /** * The type of profile selected by the user at login time, when a user has multiple identities * such as a VDM employee with a SPVM email. * 'vdm-admin' whould be selected when the authenticated user is a sysadmin who selected his superadmin profile, * instead of the regular one. * Usually defaults to 'vdm'. */ export type AccountProfile = 'vdm' | 'vdm-admin' | 'spvm'; /** * Common attributes optionally shared by all types of users. * You should test the 'type' attribute in order to strongly type the allowed attributes. */ export type CommonUserAttributes = { /** * The type of user that will specify which attributes are allowed. */ type: string; /** * The username of the user, which could be an email, a UPN or a short code depending on the 'type'. */ username: string; /** * The email of the user. */ email?: string; /** * The first name of the user. */ firstName?: string; /** * The last name of the user. */ lastName?: string; /** * The registration number of the employee as part of the HR process. * For instance: 100375065 */ registrationNumber?: string; /** * The department of the user. This is the primary organization unit that the user has been assigned to. * Note that a user could belong to multiple organization units. * Note that the department can be a 12 chars long string containing a number such as '040403040000' * or a text description such as 'DIV. PREVENTION ET SECURITE URBAINE'. */ department?: string; /** * The account profile that was selected upon logon. */ accountProfile?: AccountProfile; }; /** * The attributes of a citizen: a user consuming Montreal digital services. * There are regular citizen accounts for families as well as citizens acting on behalf of their organization. * * The ID of the citizen is managed by the DCI (Dossier Citoyen Intégré) and is known as mtlIdentityId. */ export type CitizenAttributes = CommonUserAttributes & { /** * The type of user that will specify which attributes are allowed. */ type: 'citizen'; /** * The username of the citizen which should be the email. */ username: string; /** * The email of the citizen. * This is his own mail used for registering to Montreal digital services. */ email: string; /** * The first name of the citizen. * For instance: John */ firstName: string; /** * The last name of the citizen. * For instance: DOE */ lastName: string; }; /** * The attributes of an employee: a user on the payroll of the city of Montreal. * * Note that a generic user that has all the required attributes of an employee would be detected as such, * for testing purposes. * * The ID is mapped to the username. */ export type EmployeeAttributes = CommonUserAttributes & { /** * The type of user that will specify which attributes are allowed. */ type: 'employee'; /** * The username of the employee which should be a short code starting with the letter 'u'. * For instance: uzartw1 */ username: string; /** * The email of the employee. * There are multiple domains such as montreal.ca or spvm.qc.ca */ email: string; /** * The first name of the employee. * For instance: John */ firstName: string; /** * The last name of the employee. * For instance: DOE */ lastName: string; /** * The registration number of the employee as part of the HR process. * For instance: 100375065 */ registrationNumber: string; /** * The department of the employee. This is the primary organization unit that the employee has been assigned to. * Note that an employee could belong to multiple organization units. * Note that the department can be a 12 chars long string containing a number such as '040403040000' * or a text description such as 'DIV. PREVENTION ET SECURITE URBAINE'. */ department: string; /** * The account profile that was selected upon logon. */ accountProfile: AccountProfile; /** * Specifies if the account presented as an external user is a generic account, used for testing purposes. */ isGeneric: boolean; }; /** * The attributes of an external user: a user that is not on the payroll of the city of Montreal but actively collaborates * with the city and has his own internal email and short code (starting with the letter x). * * Note that a generic user that has all the required attributes of an external user would be detected as such, * for testing purposes. * * The ID is mapped to the username. */ export type ExternalUserAttributes = CommonUserAttributes & { type: 'external'; /** * The username of the external user which should be a short code starting with the letter 'x'. * For instance: xzartw1 */ username: string; /** * The email of the external user. * The name part of the email should end with ".ext", like john.doe.ext@montreal.ca */ email?: string; /** * The first name of the user. * For instance: John */ firstName: string; /** * The last name of the user. * For instance: DOE */ lastName: string; /** * The department of the user. This is the primary organization unit that the user has been assigned to. * Note that a user could belong to multiple organization units. * Note that the department can be a 12 chars long string containing a number such as '040403040000' * or a text description such as 'DIV. PREVENTION ET SECURITE URBAINE'. */ department?: string; /** * The account profile that was selected upon logon. */ accountProfile: AccountProfile; /** * Specifies if the account presented as an external user is a generic account, used for testing purposes. */ isGeneric: boolean; }; /** * The attributes of a generic user: a fake user used for testing only, that has the right profile or role to perform specific tasks. * * Note that if a generic user has all the required attributes of an employee or an external user, then it will be detected * as such an employee or external user, for testing purposes, and not as a generic user. * However, citizens and guest users cannot be generic. * * The ID is mapped to the username. */ export type GenericUserAttributes = CommonUserAttributes & { type: 'generic'; /** * The username of the user which should be a short code starting with the letters 'cg'. * For instance: cgdsecdev2 */ username: string; /** * The first name of the user. * For instance: John */ firstName: string; /** * The last name of the user. * For instance: DOE */ lastName: string; /** * The account profile that was selected upon logon. */ accountProfile: AccountProfile; }; /** * The attributes of a guest: a user who is invited in the city of Montreal tenant in order to collaborate with some specific services. * The ID is mapped to the username. */ export type GuestUserAttributes = CommonUserAttributes & { type: 'guest'; /** * The username of the guest user which should contain the '#EXT#' suffix and belong to the "lavilledemontreal.onmicrosoft.com" domain. * For instance: john.doe_hydro.qc.ca#EXT#@lavilledemontreal.onmicrosoft.com */ username: string; /** * The email of the guest user. * The domain of the email should not belong to the City of Montreal, such as montreal.ca or spvm.qc.ca. * For instance: john.doe@hydro.qc.ca */ email: string; }; /** * An unknown user has no guaranteed attributes and defaults to optional common user attributes. * However, since it is a user, we know that it has at least a username or an email. * * The ID is mapped to the username or the email. */ export type UnknownUserAttributes = CommonUserAttributes & { type: 'unknown'; }; /** * The different sets of user attributes based on the user subtype. */ export type UserAttributes = CitizenAttributes | EmployeeAttributes | ExternalUserAttributes | GenericUserAttributes | GuestUserAttributes | UnknownUserAttributes; /************************************************************************************************ * Anonymous attributes * -------------------- * There is a single type of attributes for the anonymous identity. * ************************************************************************************************/ /** * The specific attributes for an anonymous identity. */ export type AnonymousAttributes = { type: 'anonymous'; /** * The username of the anonymous user. * For instance: srvAccAnonymous */ username: string; }; /************************************************************************************************ * Service account attributes * -------------------------- * There are 2 types of service accounts: * - client: this is a non-interactive client that used the OAuth2 client_credentials flow. * This is the only flow for Azure AD B2C and Entra ID. * - user: this is a specific user that used the OAuth2 password flow. * This flow is deprecated. * ************************************************************************************************/ /** * The specific attributes for a service account of subtype 'client'. * Note that 'client' means that the service authenticated using the OAuth2 client_credentials flow. */ export type ClientServiceAccountAttributes = { type: 'client'; }; /** * The specific attributes for a service account of subtype 'user'. * Note that 'user' means that the service authenticated using the OAuth2 password flow. (Deprecated) */ export type UserServiceAccountAttributes = { type: 'user'; /** * The username of the user service account. * For instance: SrvAccDiagCanary */ username: string; }; /** * The different sets of service account attributes based on the account subtype. */ export type ServiceAccountAttributes = ClientServiceAccountAttributes | UserServiceAccountAttributes; /*********************************************************************************************************************** * Identities: * ----------- * Each type of identity has some required properties, such as: * - the ID * - the display name * - the source of this identity * - the attributes specific to the subtype of identity * - a toString() helper method that can format the identity for auditing or logging * * We have 3 types of identity: * - user (employee, citizen, external user, generic user, guest user, unknown user...) * - service account (client, user) * - anonymous * * If we don't recognize one of those types of identity, we will default to UnknownIdentity. * * You'll have to test the type of identity before accessing the attributes, then you'll have to test the type of attributes. * * Note that a UserIdentity will default to the CommonUserAttributes type for its attributes, in order to provide * a quick access to some common user attributes. But those attributes will be optional and should be checked, * whereas the typed attributes will provide strong guarantees. * ***********************************************************************************************************************/ /** * Contains some common attributes that provide some traceability for understanding which claim we have selected for the ID of the identity, * where does the token come from and which was the internal ID of the user in the IDP. */ export type IdentitySource = { /** * The audience of the JWT, which is usually the clientID of our appId. */ aud: string; /** * The display name of the audience of the JWT, which is usually the name of our app. */ audDisplayName?: string; /** * Specifies which service issued the JWT that we parsed into an identity. * Usually, this would be 'security-identity-token-api'. */ issuer: string; /** * Specifies which IDP produced the access token that was submitted to TokenAPI. * For EntraID, this would be https://login.microsoftonline.com/9f15d2dc-8753-4f83-aac2-a58288d3a4bc/v2.0, * for Azure AD B2C https://connexion.montreal.ca, * for Gluu employee https://idp.montreal.ca, * for Gluu citizens https://auth.montreal.ca, * for anonymous tokens security-identity-anonymous-token-api */ accessTokenIssuer?: string; /** * Specifies which claim was used for the unique ID of the identity. * This could be: userName, email, mtlIdentityId, aud, sub */ claim: string; /** * Specifies the internal ID that would provide access to the user object in the IDP itself. * For Azure, this would be the 'oid' (or objectID) and for Gluu this would be the inum (or sub). */ internalId: string; /** * Specifies the realm that produced the access token. * This could be: employees, citizens, anonymous */ realm: string; /** * Specifies the name of the environment that produced the JWT. * This could be: lab, dev, accept, prod */ env?: string; }; /** * A BaseIdentity contains attributes shared by all types of Identity. * They also have a strongly typed 'attributes' property that is specific * to each kind of identity and its variants. */ export type BaseIdentity<TAttributes> = { /** * A stable unique ID for the authenticated user. * An ID can be mapped to a username, email, appId according to the type of user. * The name of the selected attribute will be specified in the source object, in the 'claim' attribute. */ id: string; /** * A display name for the authenticated user */ displayName: string; /** * The information about the source of the JWT and its associated access token. * It would also specify which claim has been selected for the unique ID of the identity. */ source: IdentitySource; /** * The attributes specific to the subtype of identity. */ attributes: TAttributes; /** * A helper function for formatting the Identity in order to log it or audit it. * This is for diagnostics only. */ toString(): string; }; /** * This is a user that can interact with the systems of the city of Montreal. * The attributes will vary according to the type of user (citizen, employee, external user...). * * Since some users might have multiple profiles and thus multiple emails (like @montreal.ca, .adm@montreal.ca or @spvm.qc.ca) * that they can select at login time, you might want to consider the "accountProfile" attribute which categorizes them. * The ID will correctly identify the physical person but not the selected role. * So, you could combine ID+accountProfile or prefer the email in some cases, like when you need to evaluate the permissions. */ export type UserIdentity<TAttributes extends UserAttributes = UserAttributes> = BaseIdentity<TAttributes> & { /** The type of identity */ type: 'user'; }; /** * This is an employee, on the payroll of the city of Montreal. * * The ID is mapped to the username. */ export type EmployeeIdentity = UserIdentity<EmployeeAttributes>; /** * This is an external user: an external collaborator or consultant that is not on the payroll of the city of Montreal, * but can access all internal services and has an email in the domain of Montreal (or SPVM). * * The ID is mapped to the username. */ export type ExternalUserIdentity = UserIdentity<ExternalUserAttributes>; /** * This is a generic user: a user shared by employees or external users in order to perform QA tests in a non production environment. * Each generic user would have its own role or profile in the tested application, allowing one to perform the tasks required by the test. * * The ID is mapped to the username. */ export type GenericUserIdentity = UserIdentity<GenericUserAttributes>; /** * This is a guest user: a user who doesn't work for the city of Montreal but needs to colloborate with a set of limited and specific services. * Note that, in theory, we could have guest users in other realms such as citizens (like sysadmins), but they cannot login at the moment and thus * we should only have tokens from the 'employees' realm. * Note that this kind of user might not have a firstName and lastName. * * The ID is mapped to the username. */ export type GuestUserIdentity = UserIdentity<GuestUserAttributes>; /** * This is a citizen: a user consuming Montreal digital services. * There are regular citizen accounts for families as well as citizens acting on behalf of their organization. * * The ID of the citizen is managed by the DCI (Dossier Citoyen Intégré) and is known as mtlIdentityId. */ export type CitizenIdentity = UserIdentity<CitizenAttributes>; /** * This is a user but we could not detect its type (employee, citizen...). * In this case, it means that the user has at least a username or an email. * All other common attributes might be defined but are not guaranteed. * * The ID of the unknown user will be mapped to the username when available, otherwise to the email. */ export type UnknownUserIdentity = UserIdentity<UnknownUserAttributes>; /** * This is an anonymous user: a user consuming some basic digitital services that don't require to be identified, * such as reporting a pot hole in the street. * * The ID is mapped to the username. */ export type AnonymousIdentity = BaseIdentity<AnonymousAttributes> & { /** The type of identity */ type: 'anonymous'; }; /** * This is a service account, without interactive logon, that allows a backend service or an automation to perform some tasks * with the required privileges (least privilege). * A service account has an ID, a display name and a secret (that expires after a few months). * * The ID is mapped to the 'aud' or the 'username', depending on the subtype. */ export type ServiceAccountIdentity<TAttributes extends ServiceAccountAttributes = ServiceAccountAttributes> = BaseIdentity<TAttributes> & { /** * The type of identity */ type: 'service-account'; }; /** * This is a service account, without interactive logon, that allows a backend service or an automation to perform some tasks * with the required privileges (least privilege). * A service account has an ID, a display name and a secret (that expires after a few months). * * The ID is mapped to the 'aud' which contains the appId in Azure or the inum in Gluu. */ export type ClientServiceAccountIdentity = ServiceAccountIdentity<ClientServiceAccountAttributes>; /** * This is the old way of managing service accounts, with a real user provisioned (that's why there is a username property). * This kind of account as been deprecated (hence the legacy part) in favor of real service accounts that don't allow interactive logon. * * The id is mapped to the username. */ export type UserServiceAccountIdentity = ServiceAccountIdentity<UserServiceAccountAttributes>; /** * An unknown identity has no specific attributes and defaults to this empty definition. */ export type UnknownAttributes = { type: 'unknown'; }; /** * This is the default identity when we could not match the proper one from the submitted JWT. * This might happen when we introduce a new type and this lib has not been updated in the client application, * but it should be very rare. * * The ID is mapped to the sub. */ export type UnknownIdentity = BaseIdentity<UnknownAttributes> & { /** * The type of identity */ type: 'unknown'; }; /** * This is the Identity of the agent performing a request. * You would have to discriminate the right identity based on the 'type' property. * All identities will have a unique ID, a display name, a source, an attributes struct and a toString() function for formatting the values. */ export type Identity = AnonymousIdentity | UserIdentity | ServiceAccountIdentity | UnknownIdentity; //# sourceMappingURL=identities.d.ts.map