@idea-ionic/auth
Version:
1,770 lines (1,767 loc) • 197 kB
JavaScript
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { inject, Injectable, Component, EventEmitter, Output, Input } from '@angular/core';
import * as i1 from '@angular/forms';
import { FormsModule } from '@angular/forms';
import { NavController, IonButton, IonText, IonCol, IonRow, IonIcon, IonLabel, IonCardSubtitle, IonCardTitle, IonCardHeader, IonImg, IonCardContent, IonCard, IonContent, IonItem, IonInput, IonCheckbox, PopoverController, ModalController, IonListHeader, IonList, IonTitle, IonButtons, IonToolbar, IonHeader } from '@ionic/angular/standalone';
import { Browser } from '@capacitor/browser';
import { isEmpty } from 'idea-toolbox';
import { IDEAEnvironment, IDEATranslationsService, IDEAStorageService, IDEAMessageService, IDEALoadingService, IDEATranslatePipe } from '@idea-ionic/common';
import { CognitoUserPool, AuthenticationDetails, CognitoUser, CognitoUserAttribute, CognitoRefreshToken } from 'amazon-cognito-identity-js';
import { ActivatedRoute, Router } from '@angular/router';
import { toCanvas } from 'qrcode';
const COMMON_PASSWORDS_TOP_1000 = [
'123456',
'password',
'12345678',
'qwerty',
'123456789',
'12345',
'1234',
'111111',
'1234567',
'dragon',
'123123',
'baseball',
'abc123',
'football',
'monkey',
'letmein',
'696969',
'shadow',
'master',
'666666',
'qwertyuiop',
'123321',
'mustang',
'1234567890',
'michael',
'654321',
'pussy',
'superman',
'1qaz2wsx',
'7777777',
'fuckyou',
'121212',
'000000',
'qazwsx',
'123qwe',
'killer',
'trustno1',
'jordan',
'jennifer',
'zxcvbnm',
'asdfgh',
'hunter',
'buster',
'soccer',
'harley',
'batman',
'andrew',
'tigger',
'sunshine',
'iloveyou',
'fuckme',
'2000',
'charlie',
'robert',
'thomas',
'hockey',
'ranger',
'daniel',
'starwars',
'klaster',
'112233',
'george',
'asshole',
'computer',
'michelle',
'jessica',
'pepper',
'1111',
'zxcvbn',
'555555',
'11111111',
'131313',
'freedom',
'777777',
'pass',
'fuck',
'maggie',
'159753',
'aaaaaa',
'ginger',
'princess',
'joshua',
'cheese',
'amanda',
'summer',
'love',
'ashley',
'6969',
'nicole',
'chelsea',
'biteme',
'matthew',
'access',
'yankees',
'987654321',
'dallas',
'austin',
'thunder',
'taylor',
'matrix',
'william',
'corvette',
'hello',
'martin',
'heather',
'secret',
'fucker',
'merlin',
'diamond',
'1234qwer',
'gfhjkm',
'hammer',
'silver',
'222222',
'88888888',
'anthony',
'justin',
'test',
'bailey',
'q1w2e3r4t5',
'patrick',
'internet',
'scooter',
'orange',
'11111',
'golfer',
'cookie',
'richard',
'samantha',
'bigdog',
'guitar',
'jackson',
'whatever',
'mickey',
'chicken',
'sparky',
'snoopy',
'maverick',
'phoenix',
'camaro',
'sexy',
'peanut',
'morgan',
'welcome',
'falcon',
'cowboy',
'ferrari',
'samsung',
'andrea',
'smokey',
'steelers',
'joseph',
'mercedes',
'dakota',
'arsenal',
'eagles',
'melissa',
'boomer',
'booboo',
'spider',
'nascar',
'monster',
'tigers',
'yellow',
'xxxxxx',
'123123123',
'gateway',
'marina',
'diablo',
'bulldog',
'qwer1234',
'compaq',
'purple',
'hardcore',
'banana',
'junior',
'hannah',
'123654',
'porsche',
'lakers',
'iceman',
'money',
'cowboys',
'987654',
'london',
'tennis',
'999999',
'ncc1701',
'coffee',
'scooby',
'0000',
'miller',
'boston',
'q1w2e3r4',
'fuckoff',
'brandon',
'yamaha',
'chester',
'mother',
'forever',
'johnny',
'edward',
'333333',
'oliver',
'redsox',
'player',
'nikita',
'knight',
'fender',
'barney',
'midnight',
'please',
'brandy',
'chicago',
'badboy',
'iwantu',
'slayer',
'rangers',
'charles',
'angel',
'flower',
'bigdaddy',
'rabbit',
'wizard',
'bigdick',
'jasper',
'enter',
'rachel',
'chris',
'steven',
'winner',
'adidas',
'victoria',
'natasha',
'1q2w3e4r',
'jasmine',
'winter',
'prince',
'panties',
'marine',
'ghbdtn',
'fishing',
'cocacola',
'casper',
'james',
'232323',
'raiders',
'888888',
'marlboro',
'gandalf',
'asdfasdf',
'crystal',
'87654321',
'12344321',
'sexsex',
'golden',
'blowme',
'bigtits',
'8675309',
'panther',
'lauren',
'angela',
'bitch',
'spanky',
'thx1138',
'angels',
'madison',
'winston',
'shannon',
'mike',
'toyota',
'blowjob',
'jordan23',
'canada',
'sophie',
'Password',
'apples',
'dick',
'tiger',
'razz',
'123abc',
'pokemon',
'qazxsw',
'55555',
'qwaszx',
'muffin',
'johnson',
'murphy',
'cooper',
'jonathan',
'liverpoo',
'david',
'danielle',
'159357',
'jackie',
'1990',
'123456a',
'789456',
'turtle',
'horny',
'abcd1234',
'scorpion',
'qazwsxedc',
'101010',
'butter',
'carlos',
'password1',
'dennis',
'slipknot',
'qwerty123',
'booger',
'asdf',
'1991',
'black',
'startrek',
'12341234',
'cameron',
'newyork',
'rainbow',
'nathan',
'john',
'1992',
'rocket',
'viking',
'redskins',
'butthead',
'asdfghjkl',
'1212',
'sierra',
'peaches',
'gemini',
'doctor',
'wilson',
'sandra',
'helpme',
'qwertyui',
'victor',
'florida',
'dolphin',
'pookie',
'captain',
'tucker',
'blue',
'liverpool',
'theman',
'bandit',
'dolphins',
'maddog',
'packers',
'jaguar',
'lovers',
'nicholas',
'united',
'tiffany',
'maxwell',
'zzzzzz',
'nirvana',
'jeremy',
'suckit',
'stupid',
'porn',
'monica',
'elephant',
'giants',
'jackass',
'hotdog',
'rosebud',
'success',
'debbie',
'mountain',
'444444',
'xxxxxxxx',
'warrior',
'1q2w3e4r5t',
'q1w2e3',
'123456q',
'albert',
'metallic',
'lucky',
'azerty',
'7777',
'shithead',
'alex',
'bond007',
'alexis',
'1111111',
'samson',
'5150',
'willie',
'scorpio',
'bonnie',
'gators',
'benjamin',
'voodoo',
'driver',
'dexter',
'2112',
'jason',
'calvin',
'freddy',
'212121',
'creative',
'12345a',
'sydney',
'rush2112',
'1989',
'asdfghjk',
'red123',
'bubba',
'4815162342',
'passw0rd',
'trouble',
'gunner',
'happy',
'fucking',
'gordon',
'legend',
'jessie',
'stella',
'qwert',
'eminem',
'arthur',
'apple',
'nissan',
'bullshit',
'bear',
'america',
'1qazxsw2',
'nothing',
'parker',
'4444',
'rebecca',
'qweqwe',
'garfield',
'01012011',
'beavis',
'69696969',
'jack',
'asdasd',
'december',
'2222',
'102030',
'252525',
'11223344',
'magic',
'apollo',
'skippy',
'315475',
'girls',
'kitten',
'golf',
'copper',
'braves',
'shelby',
'godzilla',
'beaver',
'fred',
'tomcat',
'august',
'buddy',
'airborne',
'1993',
'1988',
'lifehack',
'qqqqqq',
'brooklyn',
'animal',
'platinum',
'phantom',
'online',
'xavier',
'darkness',
'blink182',
'power',
'fish',
'green',
'789456123',
'voyager',
'police',
'travis',
'12qwaszx',
'heaven',
'snowball',
'lover',
'abcdef',
'00000',
'pakistan',
'007007',
'walter',
'playboy',
'blazer',
'cricket',
'sniper',
'hooters',
'donkey',
'willow',
'loveme',
'saturn',
'therock',
'redwings',
'bigboy',
'pumpkin',
'trinity',
'williams',
'tits',
'nintendo',
'digital',
'destiny',
'topgun',
'runner',
'marvin',
'guinness',
'chance',
'bubbles',
'testing',
'fire',
'november',
'minecraft',
'asdf1234',
'lasvegas',
'sergey',
'broncos',
'cartman',
'private',
'celtic',
'birdie',
'little',
'cassie',
'babygirl',
'donald',
'beatles',
'1313',
'dickhead',
'family',
'12121212',
'school',
'louise',
'gabriel',
'eclipse',
'fluffy',
'147258369',
'lol123',
'explorer',
'beer',
'nelson',
'flyers',
'spencer',
'scott',
'lovely',
'gibson',
'doggie',
'cherry',
'andrey',
'snickers',
'buffalo',
'pantera',
'metallica',
'member',
'carter',
'qwertyu',
'peter',
'alexande',
'steve',
'bronco',
'paradise',
'goober',
'5555',
'samuel',
'montana',
'mexico',
'dreams',
'michigan',
'cock',
'carolina',
'yankee',
'friends',
'magnum',
'surfer',
'poopoo',
'maximus',
'genius',
'cool',
'vampire',
'lacrosse',
'asd123',
'aaaa',
'christin',
'kimberly',
'speedy',
'sharon',
'carmen',
'111222',
'kristina',
'sammy',
'racing',
'ou812',
'sabrina',
'horses',
'0987654321',
'qwerty1',
'pimpin',
'baby',
'stalker',
'enigma',
'147147',
'star',
'poohbear',
'boobies',
'147258',
'simple',
'bollocks',
'12345q',
'marcus',
'brian',
'1987',
'qweasdzxc',
'drowssap',
'hahaha',
'caroline',
'barbara',
'dave',
'viper',
'drummer',
'action',
'einstein',
'bitches',
'genesis',
'hello1',
'scotty',
'friend',
'forest',
'010203',
'hotrod',
'google',
'vanessa',
'spitfire',
'badger',
'maryjane',
'friday',
'alaska',
'1232323q',
'tester',
'jester',
'jake',
'champion',
'billy',
'147852',
'rock',
'hawaii',
'badass',
'chevy',
'420420',
'walker',
'stephen',
'eagle1',
'bill',
'1986',
'october',
'gregory',
'svetlana',
'pamela',
'1984',
'music',
'shorty',
'westside',
'stanley',
'diesel',
'courtney',
'242424',
'kevin',
'porno',
'hitman',
'boobs',
'mark',
'12345qwert',
'reddog',
'frank',
'qwe123',
'popcorn',
'patricia',
'aaaaaaaa',
'1969',
'teresa',
'mozart',
'buddha',
'anderson',
'paul',
'melanie',
'abcdefg',
'security',
'lucky1',
'lizard',
'denise',
'3333',
'a12345',
'123789',
'ruslan',
'stargate',
'simpsons',
'scarface',
'eagle',
'123456789a',
'thumper',
'olivia',
'naruto',
'1234554321',
'general',
'cherokee',
'a123456',
'vincent',
'Usuckballz1',
'spooky',
'qweasd',
'cumshot',
'free',
'frankie',
'douglas',
'death',
'1980',
'loveyou',
'kitty',
'kelly',
'veronica',
'suzuki',
'semperfi',
'penguin',
'mercury',
'liberty',
'spirit',
'scotland',
'natalie',
'marley',
'vikings',
'system',
'sucker',
'king',
'allison',
'marshall',
'1979',
'098765',
'qwerty12',
'hummer',
'adrian',
'1985',
'vfhbyf',
'sandman',
'rocky',
'leslie',
'antonio',
'98765432',
'4321',
'softball',
'passion',
'mnbvcxz',
'bastard',
'passport',
'horney',
'rascal',
'howard',
'franklin',
'bigred',
'assman',
'alexander',
'homer',
'redrum',
'jupiter',
'claudia',
'55555555',
'141414',
'zaq12wsx',
'shit',
'patches',
'nigger',
'cunt',
'raider',
'infinity',
'andre',
'54321',
'galore',
'college',
'russia',
'kawasaki',
'bishop',
'77777777',
'vladimir',
'money1',
'freeuser',
'wildcats',
'francis',
'disney',
'budlight',
'brittany',
'1994',
'00000000',
'sweet',
'oksana',
'honda',
'domino',
'bulldogs',
'brutus',
'swordfis',
'norman',
'monday',
'jimmy',
'ironman',
'ford',
'fantasy',
'9999',
'7654321',
'PASSWORD',
'hentai',
'duncan',
'cougar',
'1977',
'jeffrey',
'house',
'dancer',
'brooke',
'timothy',
'super',
'marines',
'justice',
'digger',
'connor',
'patriots',
'karina',
'202020',
'molly',
'everton',
'tinker',
'alicia',
'rasdzv3',
'poop',
'pearljam',
'stinky',
'naughty',
'colorado',
'123123a',
'water',
'test123',
'ncc1701d',
'motorola',
'ireland',
'asdfg',
'slut',
'matt',
'houston',
'boogie',
'zombie',
'accord',
'vision',
'bradley',
'reggie',
'kermit',
'froggy',
'ducati',
'avalon',
'6666',
'9379992',
'sarah',
'saints',
'logitech',
'chopper',
'852456',
'simpson',
'madonna',
'juventus',
'claire',
'159951',
'zachary',
'yfnfif',
'wolverin',
'warcraft',
'hello123',
'extreme',
'penis',
'peekaboo',
'fireman',
'eugene',
'brenda',
'123654789',
'russell',
'panthers',
'georgia',
'smith',
'skyline',
'jesus',
'elizabet',
'spiderma',
'smooth',
'pirate',
'empire',
'bullet',
'8888',
'virginia',
'valentin',
'psycho',
'predator',
'arizona',
'134679',
'mitchell',
'alyssa',
'vegeta',
'titanic',
'christ',
'goblue',
'fylhtq',
'wolf',
'mmmmmm',
'kirill',
'indian',
'hiphop',
'baxter',
'awesome',
'people',
'danger',
'roland',
'mookie',
'741852963',
'1111111111',
'dreamer',
'bambam',
'arnold',
'1981',
'skipper',
'serega',
'rolltide',
'elvis',
'changeme',
'simon',
'1q2w3e',
'lovelove',
'fktrcfylh',
'denver',
'tommy',
'mine',
'loverboy',
'hobbes',
'happy1',
'alison',
'nemesis',
'chevelle',
'cardinal',
'burton',
'wanker',
'picard',
'151515',
'tweety',
'michael1',
'147852369',
'12312',
'xxxx',
'windows',
'turkey',
'456789',
'1974',
'vfrcbv',
'sublime',
'1975',
'galina',
'bobby',
'newport',
'manutd',
'daddy',
'american',
'alexandr',
'1966',
'victory',
'rooster',
'qqq111',
'madmax',
'electric',
'bigcock',
'a1b2c3',
'wolfpack',
'spring',
'phpbb',
'lalala',
'suckme',
'spiderman',
'eric',
'darkside',
'classic',
'raptor',
'123456789q',
'hendrix',
'1982',
'wombat',
'avatar',
'alpha',
'zxc123',
'crazy',
'hard',
'england',
'brazil',
'1978',
'01011980',
'wildcat',
'polina',
'freepass'
];
/**
* Cognito wrapper to manage the authentication flow.
*
* Note: in IDEA's Cognito users pools, the email is an alias of the username.
*/
class IDEAAuthService {
constructor() {
this._env = inject(IDEAEnvironment);
this._translate = inject(IDEATranslationsService);
this._storage = inject(IDEAStorageService);
this.deviceKeyAttribute = 'custom:'.concat(this._env.idea.project);
this.userPool = new CognitoUserPool({
UserPoolId: this._env.aws.cognito.userPoolId,
ClientId: this._env.aws.cognito.userPoolClientId
});
this.mfaProjectName = this._env.idea.auth.title || this._env.idea.project;
this.passwordPolicy = this._env.idea.auth.passwordPolicy;
}
/**
* Prepare the necessary structure to get authorized in Cognito.
*/
prepareAuthDetails(username, pwd) {
return new AuthenticationDetails({ Username: username, Password: pwd });
}
/**
* Prepare the necessary structure to identify a Cognito user.
*/
prepareCognitoUser(username) {
return new CognitoUser({ Username: username, Pool: this.userPool });
}
/**
* Prepare a user attribute (they are all strings) in Cognito's format.
*/
prepareUserAttribute(name, value) {
return new CognitoUserAttribute({ Name: name, Value: value });
}
/**
* Perform a login through username and password.
*/
login(username, password) {
return new Promise((resolve, reject) => {
const user = this.prepareCognitoUser(username);
user.authenticateUser(this.prepareAuthDetails(username, password), {
onSuccess: () => resolve(this._env.idea.auth.forceLoginWithMFA ? LoginOutcomeActions.MFA_SETUP : LoginOutcomeActions.NONE),
onFailure: (err) => reject(err),
newPasswordRequired: () => {
this.challengeUsername = username;
this.challengePassword = password;
resolve(LoginOutcomeActions.NEW_PASSWORD);
},
totpRequired: () => {
this.challengeUsername = username;
this.challengePassword = password;
resolve(LoginOutcomeActions.MFA_CHALLENGE);
}
});
});
}
/**
* Complete the new password flow in the authentication.
*/
confirmNewPassword(newPassword) {
return new Promise((resolve, reject) => {
const user = this.prepareCognitoUser(this.challengeUsername);
user.authenticateUser(this.prepareAuthDetails(this.challengeUsername, this.challengePassword), {
onSuccess: () => resolve(),
onFailure: (err) => reject(err),
newPasswordRequired: () => user.completeNewPasswordChallenge(newPassword, {}, {
onSuccess: () => {
this.challengeUsername = null;
this.challengePassword = null;
resolve();
},
onFailure: (err) => reject(err)
})
});
});
}
/**
* Complete the MFA challenge flow in the authentication.
*/
completeMFAChallenge(otpCode) {
return new Promise((resolve, reject) => {
const user = this.prepareCognitoUser(this.challengeUsername);
user.authenticateUser(this.prepareAuthDetails(this.challengeUsername, this.challengePassword), {
onSuccess: () => resolve(),
onFailure: (err) => reject(err),
totpRequired: (challengeName) => user.sendMFACode(otpCode, {
onSuccess: () => {
this.challengeUsername = null;
this.challengePassword = null;
resolve();
},
onFailure: err => reject(err)
}, challengeName)
});
});
}
/**
* Register a new user a set its default attributes.
*/
register(username, password, attributes) {
attributes = attributes || {};
return new Promise((resolve, reject) => {
// add attributes like the email address and the fullname
const attrs = [];
for (const prop in attributes)
if (attributes[prop])
attrs.push(this.prepareUserAttribute(prop, attributes[prop]));
// add the email, which is equal to the username for most of our Pools
if (!attributes.email)
attrs.push(this.prepareUserAttribute('email', username));
// register the new user to the pool
this.userPool.signUp(username, password, attrs, null, (err, res) => {
if (err)
return reject(err);
this.newAccountJustRegistered = username;
resolve(res.user);
});
});
}
/**
* In case a new account has just been registered, return the username.
*/
getNewAccountJustRegistered() {
return this.newAccountJustRegistered;
}
/**
* Confirm a new registration through the confirmation code sent by Cognito.
*/
confirmRegistration(username, code) {
return new Promise((resolve, reject) => {
this.prepareCognitoUser(username).confirmRegistration(code, true, (err) => err ? reject(err) : resolve());
});
}
/**
* Send again a confirmation code for a new registration.
*/
resendConfirmationCode(username) {
return new Promise((resolve, reject) => {
this.prepareCognitoUser(username).resendConfirmationCode((err) => (err ? reject(err) : resolve()));
});
}
/**
* Logout the currently signed-in user.
*/
logout(options) {
options = Object.assign({ global: false }, options);
return new Promise((resolve, reject) => {
// remove the refresh token previosly saved
this._storage.remove('AuthRefreshToken').then(() =>
// remove the optional auth details
this._storage.remove('AuthUserDetails').then(() => {
// get the user and the session
const user = this.userPool.getCurrentUser();
user.getSession(async (err) => {
// if the session is active, run the online sign-out; otherwise, only the local data has been deleted
if (err)
return resolve();
// if a reference to the device was saved, forget it
if (this._env.idea.auth.singleSimultaneousSession)
await this.setCurrentDeviceForProject(null);
// sign-out from the pool (terminate the current session or all the sessions)
if (options.global)
user.globalSignOut({ onSuccess: () => resolve(), onFailure: err => reject(err) });
else {
user.signOut();
resolve();
}
});
}));
});
}
/**
* Send a password reset request.
*/
forgotPassword(username) {
return new Promise((resolve, reject) => {
this.prepareCognitoUser(username).forgotPassword({
onSuccess: () => resolve(),
onFailure: (err) => reject(err)
});
});
}
/**
* Confirm a new password after a password reset request.
*/
confirmPassword(username, code, newPwd) {
return new Promise((resolve, reject) => {
this.prepareCognitoUser(username).confirmPassword(code, newPwd, {
onSuccess: () => resolve(),
onFailure: (err) => reject(err)
});
});
}
/**
* Gets the URL for enabling MFA. This URL can be used to generate a QR Code to read with an authenticator app.
*/
getURLForEnablingMFA() {
return new Promise((resolve, reject) => {
const user = this.userPool.getCurrentUser();
if (!user)
return reject();
user.getSession((err) => {
if (err)
return reject(err);
user.associateSoftwareToken({
associateSecretCode: (secretCode) => resolve(`otpauth://totp/${encodeURI(this.mfaProjectName)}?secret=${secretCode}`),
onFailure: (err) => reject(err)
});
});
});
}
/**
* Check whether the user has MFA enabled.
*/
checkIfUserHasMFAEnabled(bypassCache = false) {
return new Promise((resolve, reject) => {
const user = this.userPool.getCurrentUser();
if (!user)
return reject();
user.getSession((err) => {
if (err)
return reject(err);
user.getUserData((err, data) => {
if (err)
return reject(err);
const isMFAEnabled = !!(data.UserMFASettingList && data.UserMFASettingList.includes('SOFTWARE_TOKEN_MFA'));
resolve(isMFAEnabled);
}, { bypassCache });
});
});
}
/**
* Configure a MFA device for the user.
*/
setMFADevice(otp, mfaDeviceName, enabled = true, preferred = true) {
return new Promise((resolve, reject) => {
const user = this.userPool.getCurrentUser();
if (!user)
return reject();
user.getSession((err) => {
if (err)
return reject(err);
user.verifySoftwareToken(otp, mfaDeviceName, {
onSuccess: () => user.setUserMfaPreference(null, { Enabled: enabled, PreferredMfa: preferred }, (err) => err ? reject(err) : resolve()),
onFailure: (err) => reject(err)
});
});
});
}
/**
* Enable a new MFA device for the user by inserting an OTP code generated by it.
*/
enableMFA(otp, mfaDeviceName = 'default') {
return this.setMFADevice(otp, mfaDeviceName, true, true);
}
/**
* Disable MFA for a user by inserting an OTP code generated by it.
*/
disableMFA(otp, mfaDeviceName = 'default') {
return this.setMFADevice(otp, mfaDeviceName, false, false);
}
/**
* Check if a user is currently authenticated.
* @param offlineAllowed if set and if offline, skip authentication and retrieve data locally
* @param getFreshIdTokenOnExp cb function to execute when the idToken is refreshed
*/
isAuthenticated(offlineAllowed, getFreshIdTokenOnExp) {
return new Promise((resolve, reject) => {
if (offlineAllowed && !navigator.onLine) {
this._storage.get('AuthUserDetails').then(userDetails => resolve({ idToken: null, userDetails }));
// re-execute the method when back online, so that you can retrieve a token to make requests
window.addEventListener('online', () => this.isAuthenticated(true, getFreshIdTokenOnExp)
// set the new token as if it was refreshed
.then(result => getFreshIdTokenOnExp(result.idToken)));
}
else {
const user = this.userPool.getCurrentUser();
if (!user)
return reject();
user.getSession((err, session) => {
if (err)
return reject(err);
user.getUserData((err, data) => {
if (err)
return reject(err);
const isMFAEnabled = !!(data.UserMFASettingList && data.UserMFASettingList.includes('SOFTWARE_TOKEN_MFA'));
if (!isMFAEnabled && this._env.idea.auth.forceLoginWithMFA)
return reject(new Error(LoginOutcomeActions.MFA_SETUP));
user.getUserAttributes((e, attributes) => {
if (e)
return reject(e);
// remap user attributes
const userDetails = {};
attributes.forEach((a) => (userDetails[a.getName()] = a.getValue()));
// add the user's groups to the attributes
const sessionInfo = session.getAccessToken().decodePayload();
const groups = sessionInfo['cognito:groups'] || [];
userDetails['groups'] = groups;
// run some checks considering the user's groups and devices (based on the project's configuration)
this.runPostAuthChecks(userDetails, err => {
// in case some check failed, reject the authorisation flow
if (err)
return reject(err);
// (async) save the refresh token so it can be accessed by other procedures
this._storage.set('AuthRefreshToken', session.getRefreshToken().getToken());
// set a timer to manage the autorefresh of the idToken (through the refreshToken)
setTimeout(() => this.refreshSession(user, session.getRefreshToken().getToken(), getFreshIdTokenOnExp), 15 * 60 * 1000); // every 15 minutes
// (async) if offlineAllowed, save data locally, to use it next time we'll be offline
if (offlineAllowed)
this._storage.set('AuthUserDetails', userDetails);
// return the idToken (to use with API)
resolve({ idToken: session.getIdToken().getJwtToken(), userDetails });
});
});
}, { bypassCache: true });
});
}
});
}
/**
* Helper to refresh the session every N minutes.
*/
refreshSession(user, refreshToken, callback) {
user.refreshSession(new CognitoRefreshToken({ RefreshToken: refreshToken }), (err, session) => {
if (err) {
// try again in 1 minute
setTimeout(() => this.refreshSession(user, refreshToken, callback), 1 * 60 * 1000);
}
else {
// (async) save the refresh token so it can be accessed by other procedures
this._storage.set('AuthRefreshToken', session.getRefreshToken().getToken());
// repeat every 15 minutes
setTimeout(() => this.refreshSession(user, session.getRefreshToken().getToken(), callback), 15 * 60 * 1000);
// run the callback action, if set
if (callback)
callback(session.getIdToken().getJwtToken());
}
});
}
/**
* Run some post-auth checks, based on the users groups and on the app's configuration:
* - users in the Cognito's "admins" grup skip all the following rules.
* - users in the Cognito's "robots" group can't sign-into front-ends (they serve only back-end purposes).
* - if `env.idea.auth.singleSimultaneousSession` is on, make sure there is only one active session per user.
*/
runPostAuthChecks(userDetails, callback) {
const groups = userDetails['groups'];
// skip checks if the user is in the "admins" group
const isAdmin = groups.some(x => x === 'admins');
if (isAdmin)
return callback();
// users in the "robots" group can't sign-into front-ends (they serve only back-end purposes)
const isRobot = groups.some(x => x === 'robots');
if (isRobot)
return callback(new Error('ROBOT_USER'));
// in case the project limits each account to only one simultaneous session, run a check
if (this._env.idea.auth.singleSimultaneousSession)
return this.checkForSimultaneousSessions(userDetails, callback);
// otherwise, we're done
callback();
}
/**
* Check whether the user signed-into multiple devices.
*/
async checkForSimultaneousSessions(userDetails, callback) {
// get or create a key for the current device (~random)
let currentDeviceKey = await this._storage.get('AuthDeviceKey');
if (!currentDeviceKey) {
const randomKey = Math.random().toString(36).substring(10);
currentDeviceKey = randomKey.concat(String(Date.now()));
await this._storage.set('AuthDeviceKey', currentDeviceKey);
}
// check whether the last device to sign-into this project (if any) is the current one
const lastDeviceToSignIntoThisProject = userDetails[this.deviceKeyAttribute];
// if it's the first user's device to sign-in, save the reference in Cognito and resolve
if (!lastDeviceToSignIntoThisProject) {
this.setCurrentDeviceForProject(currentDeviceKey)
.then(() => callback())
.catch(() => callback(new Error('CANT_SET_DEVICE')));
// if the device didn't change, resolve
}
else if (lastDeviceToSignIntoThisProject === currentDeviceKey)
callback();
// othwerwise, report there is more than one simultaneous session
else
callback(new Error('SIMULTANEOUS_SESSION'));
}
/**
* Set (or reset) the current user's device (by key) in the current project (stored in Cognito).
*/
setCurrentDeviceForProject(deviceKey) {
const attributes = {};
// note: an empty string will remove the attribute from Cognito
attributes[this.deviceKeyAttribute] = deviceKey || '';
return this.updateUserAttributes(attributes);
}
/**
* Update the currently logged in user's attributes.
*/
updateUserAttributes(attributes) {
return new Promise((resolve, reject) => {
// prepare the attributes we want to change
const attrs = new Array();
for (const prop in attributes)
if (attributes[prop] !== undefined)
attrs.push(this.prepareUserAttribute(prop, attributes[prop]));
const user = this.userPool.getCurrentUser();
if (!user)
return reject();
// we need to get the session before to make changes
user.getSession((err) => {
if (err)
reject(err);
else
user.updateAttributes(attrs, (e) => (e ? reject(e) : resolve()));
});
});
}
/**
* Validate the password against the policy set in the environments configuration.
* In case there are errors, they are returned as an array of strings.
*/
validatePasswordAgainstPolicy(password) {
const errors = [];
if (password?.trim().length < this.passwordPolicy.minLength)
errors.push('MIN_LENGTH');
if (this.passwordPolicy.requireDigits && !/\d/.test(password))
errors.push('REQUIRE_DIGITS');
if (this.passwordPolicy.requireLowercase && !/[a-z]/.test(password))
errors.push('REQUIRE_LOWERCASE');
if (this.passwordPolicy.requireSymbols && !/[\^\$\*\.\_\~\`\+\=@\!\?\>\<\:\;\\\,\#\%\&]/.test(password))
errors.push('REQUIRE_SYMBOLS');
if (this.passwordPolicy.requireUppercase && !/[A-Z]/.test(password))
errors.push('REQUIRE_UPPERCASE');
return errors;
}
/**
* Validate the password against offline and online databases, to avoid common and exploited passwords.
*/
async validatePasswordAgainstDatabases(password, customStringsToAvoid) {
const errors = [];
let pwd = (password || '').trim().toLowerCase();
const pwdContains = (str) => pwd.includes((str || '').trim().toLowerCase());
const projectReferencesToAvoid = [
this._env.idea.project,
this._env.idea.auth.title,
this._translate._('COMMON.APP_NAME')
];
if (projectReferencesToAvoid.some(x => pwdContains(x)))
errors.push('COMMON_PASSWORD_PROJECT');
if (customStringsToAvoid && customStringsToAvoid.some(x => pwdContains(x)))
errors.push('COMMON_PASSWORD_CUSTOM');
if (COMMON_PASSWORDS_TOP_1000.some(x => pwdContains(x)))
errors.push('COMMON_PASSWORD_INTERNAL');
if (errors.length === 0) {
const pwdSHA1 = await SHA1(password);
if (pwdSHA1) {
const res = await fetch('https://api.pwnedpasswords.com/range/'.concat(pwdSHA1.slice(0, 5)));
if (res.status === 200) {
const pwnedPasswordsRawSuffixes = await res.text();
if (pwnedPasswordsRawSuffixes.includes(pwdSHA1.slice(5)))
errors.push('COMMON_PASSWORD_EXTERNAL');
}
}
}
return errors;
}
/**
* Get a complete password policy pattern, based on the environments configuration, to use on password input fields.
* Note: some of the symbols couldn't be included because unsupported by the input[pattern] attribute.
*/
getPasswordPolicyPatternForInput() {
let pattern = '';
if (this.passwordPolicy.requireDigits)
pattern += `(?=.*[0-9])`;
if (this.passwordPolicy.requireLowercase)
pattern += `(?=.*[a-z])`;
if (this.passwordPolicy.requireSymbols)
pattern += `(?=.*[\^\$\*\.\_\~\`\+\=@\!\?\>\<\:\;\\\,\#\%\&])`;
if (this.passwordPolicy.requireUppercase)
pattern += `(?=.*[A-Z])`;
pattern += `.{${this.passwordPolicy.minLength},}`;
return pattern;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: IDEAAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: IDEAAuthService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: IDEAAuthService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
/**
* The possible actions following a successful login.
*/
var LoginOutcomeActions;
(function (LoginOutcomeActions) {
LoginOutcomeActions["NONE"] = "none";
LoginOutcomeActions["NEW_PASSWORD"] = "newPassword";
LoginOutcomeActions["MFA_CHALLENGE"] = "mfaChallenge";
LoginOutcomeActions["MFA_SETUP"] = "mfaSetup";
})(LoginOutcomeActions || (LoginOutcomeActions = {}));
/**
* Get the SHA1 hex uppercase hash of a string, using web crypto.
*/
const SHA1 = async (str) => {
if (!window.crypto)
return null;
const msgUint8 = new TextEncoder().encode(str);
const hashBuffer = await window.crypto.subtle.digest('SHA-1', msgUint8);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex.toUpperCase();
};
class IDEASignInPage {
constructor() {
this._env = inject(IDEAEnvironment);
this._nav = inject(NavController);
this._message = inject(IDEAMessageService);
this._loading = inject(IDEALoadingService);
this._translate = inject(IDEATranslationsService);
this._auth = inject(IDEAAuthService);
this.doneExternalProviderCheck = false;
this.agreementsCheck = true;
this.newAccountRegistered = false;
this.darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
this.title = this._env.idea.auth.title;
this.registrationPossible = this._env.idea.auth.registrationIsPossible;
this.hasIntroPage = this._env.idea.auth.hasIntroPage;
this.website = this._env.idea.auth.website;
this.externalProviders = this._env.aws.cognito?.externalProviders || [];
}
ionViewDidEnter() {
// manage the scenario in which we just created a new account (show a explanatory message: email must be confirmed)
this.newAccountRegistered = !!this._auth.getNewAccountJustRegistered();
if (this.newAccountRegistered)
this.email = this._auth.getNewAccountJustRegistered();
}
async login() {
if (!this.agreementsCheck)
return;
try {
this.errorMsg = null;
await this._loading.show();
const loginAction = await this._auth.login(this.email, this.password);
if (loginAction === LoginOutcomeActions.NEW_PASSWORD)
this._nav.navigateForward(['auth', 'new-password']);
else if (loginAction === LoginOutcomeActions.MFA_CHALLENGE)
this._nav.navigateForward(['auth', 'mfa-challenge']);
else if (loginAction === LoginOutcomeActions.MFA_SETUP)
this._nav.navigateForward(['auth', 'setup-mfa']);
else
window.location.assign('');
}
catch (err) {
if (err.name === 'UserNotConfirmedException')
this.errorMsg = this._translate._('IDEA_AUTH.CONFIRM_YOUR_EMAIL_TO_LOGIN');
else if (err.name === 'UserLambdaValidationException' &&
err.message?.includes('@IDEA_COGNITO_TRANSITION'))
this.errorMsg = this._translate._('IDEA_AUTH.CHANGE_YOUR_PASSWORD_TO_LOGIN');
this._message.error('IDEA_AUTH.AUTHENTICATION_FAILED');
}
finally {
this._loading.hide();
}
}
checkForExternalProviderEmail() {
if (isEmpty(this.email, 'email'))
return;
const emailDomain = this.email.split('@')[1];
this.doneExternalProviderCheck = true;
const provider = this.externalProviders.find(p => p.emailDomains.includes(emailDomain));
if (provider)
this._nav.navigateForward(['auth', provider.type], { queryParams: { provider: provider.name, go: true } });
}
goToIntro() {
this._nav.navigateBack(['intro']);
}
goToForgotPassword() {
this._nav.navigateForward(['auth', 'forgot-password']);
}
goToRegistration() {
this._nav.navigateForward(['auth', 'sign-up']);
}
translationExists(key) {
return !!this._translate._(key);
}
async openLink(url) {
await Browser.open({ url });
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: IDEASignInPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: IDEASignInPage, isStandalone: true, selector: "idea-sign-in", ngImport: i0, template: `
<ion-content>
@if (newAccountRegistered) {
<ion-card color="warning">
<ion-card-content>{{ 'IDEA_AUTH.CONFIRM_YOUR_EMAIL_TO_LOGIN' | translate }}</ion-card-content>
</ion-card>
}
<form class="flexBox">
<div>
<ion-card class="authCard">
<ion-img
[title]="'IDEA_AUTH.SERVICE_LOGO' | translate"
[src]="darkMode ? 'assets/icons/icon-auth-alt.svg' : 'assets/icons/icon-auth.svg'"
/>
<ion-card-header>
@if (title) {
<ion-card-title color="primary">{{ title }}</ion-card-title>
}
@if (translationExists('IDEA_VARIABLES.TAGLINE')) {
<ion-card-subtitle> {{ 'IDEA_VARIABLES.TAGLINE' | translate }} </ion-card-subtitle>
}
</ion-card-header>
<ion-card-content>
@if (errorMsg) {
<p testId="signin.error" class="errorBox">
<b>{{ 'IDEA_AUTH.WARNING' | translate }}.</b> {{ errorMsg }}
</p>
}
<ion-item>
<ion-label position="inline">
<ion-icon name="person-circle" color="primary" />
</ion-label>
<ion-input
testId="signin.email"
type="email"
inputmode="email"
pattern="[A-Za-z0-9._%+-]{2,}@[a-zA-Z-_.]{2,}[.]{1}[a-zA-Z]{2,}"
spellcheck="false"
autocorrect="off"
autocomplete="email"
[placeholder]="'IDEA_AUTH.EMAIL' | translate"
[title]="'IDEA_AUTH.EMAIL_HINT' | translate"
[disabled]="externalProviders.length && doneExternalProviderCheck"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="email"
(keyup.enter)="externalProviders.length ? checkForExternalProviderEmail() : login()"
/>
@if (externalProviders.length && doneExternalProviderCheck) {
<ion-button
slot="end"
fill="clear"
[title]="'IDEA_AUTH.EDIT_EMAIL' | translate"
(click)="doneExternalProviderCheck = false"
>
<ion-icon icon="pencil" slot="icon-only" />
</ion-button>
}
</ion-item>
@if (externalProviders.length && !doneExternalProviderCheck) {
<ion-button
testId="continueButton"
expand="block"
[disabled]="!agreementsCheck"
[title]="'IDEA_AUTH.CONTINUE_HINT' | translate"
(click)="checkForExternalProviderEmail()"
>
{{ 'IDEA_AUTH.CONTINUE' | translate }}
</ion-button>
} @else {
<ion-item>
<ion-label position="inline">
<ion-icon name="key" color="primary" />
</ion-label>
<ion-input
testId="signin.password"
id="current-password"
type="password"
spellcheck="false"
autocorrect="off"
autocomplete="current-password"
[clearOnEdit]="false"
[placeholder]="'IDEA_AUTH.PASSWORD' | translate"
[title]="'IDEA_AUTH.PASSWORD_HINT' | translate"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="password"
(keyup.enter)="login()"
/>
</ion-item>
@if (
!registrationPossible &&
(translationExists('IDEA_VARIABLES.TERMS_AND_CONDITIONS_URL') ||
translationExists('IDEA_VARIABLES.PRIVACY_POLICY_URL'))
) {
<ion-row class="agreementsAcceptanceRow">
<ion-col>
<ion-checkbox
size="small"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="agreementsCheck"
/>
{{ 'IDEA_AUTH.AGREEMENTS_CHECK_LOGIN' | translate }}:
@if (translationExists('IDEA_VARIABLES.TERMS_AND_CONDITIONS_URL')) {
<a
ion-text
testId="openTermsAndConditionsButton"
color="primary"
target="_blank"
[title]="'IDEA_AUTH.TERMS_AND_CONDITIONS_HINT' | translate"
[href]="'IDEA_VARIABLES.TERMS_AND_CONDITIONS_URL' | translate"
>
{{ 'IDEA_AUTH.TERMS_AND_CONDITIONS' | translate }}
</a>
}
@if (
translationExists('IDEA_VARIABLES.PRIVACY_POLICY_URL') &&
translationExists('IDEA_VARIABLES.TERMS_AND_CONDITIONS_URL')
) {
<ion-text> & </ion-text>
}
@if (translationExists('IDEA_VARIABLES.PRIVACY_POLICY_URL')) {
<a
ion-text
testId="openPrivacyPolicyButton"
color="primary"
target="_blank"
[title]="'IDEA_AUTH.PRIVACY_POLICY_HINT' | translate"
[href]="'IDEA_VARIABLES.PRIVACY_POLICY_URL' | translate"
>
{{ 'IDEA_AUTH.PRIVACY_POLICY' | translate }}
</a>
}
</ion-col>
</ion-row>
}
<ion-button
testId="signInButton"
expand="block"
[disabled]="!agreementsCheck"
[title]="'IDEA_AUTH.SIGN_IN_HINT' | translate"
(click)="login()"
>
{{ 'IDEA_AUTH.SIGN_IN' | translate }}
</ion-button>
@if (registrationPossible) {
<ion-button
testId="createAnAccountButton"
fill="clear"
expand="block"
class="smallCaseButton"
[title]="'IDEA_AUTH.CREATE_AN_ACCOUNT_HINT' | translate"
(click)="goToRegistration()"
>
{{ 'IDEA_AUTH.CREATE_AN_ACCOUNT' | translate }}
</ion-button>
}
<ion-button
testId="forgotPasswordButton"
fill="clear"
expand="block"
class="smallCaseButton"
[title]="'IDEA_AUTH.I_FORGOT_MY_PASSWORD_HINT' |