UNPKG

slr-angularx-social-login

Version:

Social login and authentication module for Angular 13. Supports authentication with Google, Facebook, Amazon, and VK. Can be extended to other providers also.

878 lines (865 loc) 33.5 kB
import * as i0 from '@angular/core'; import { Injectable, Inject, NgModule, Optional, SkipSelf } from '@angular/core'; import { ReplaySubject, AsyncSubject } from 'rxjs'; import { CommonModule } from '@angular/common'; import { __awaiter } from 'tslib'; class BaseLoginProvider { constructor() { } loadScript(id, src, onload, parentElement = null) { // get document if platform is only browser if (typeof document !== 'undefined' && !document.getElementById(id)) { let signInJS = document.createElement('script'); signInJS.async = true; signInJS.src = src; signInJS.onload = onload; if (!parentElement) { parentElement = document.head; } parentElement.appendChild(signInJS); } } } class SocialUser { } class GoogleLoginProvider extends BaseLoginProvider { constructor(clientId, initOptions = { scope: 'email' }) { super(); this.clientId = clientId; this.initOptions = initOptions; } initialize() { return new Promise((resolve, reject) => { try { this.loadScript(GoogleLoginProvider.PROVIDER_ID, 'https://apis.google.com/js/platform.js', () => { gapi.load('auth2', () => { this.auth2 = gapi.auth2.init(Object.assign(Object.assign({}, this.initOptions), { client_id: this.clientId })); this.auth2 .then(() => { resolve(); }) .catch((err) => { reject(err); }); }); }); } catch (err) { reject(err); } }); } getLoginStatus(loginStatusOptions) { const options = Object.assign(Object.assign({}, this.initOptions), loginStatusOptions); return new Promise((resolve, reject) => { if (this.auth2.isSignedIn.get()) { let user = new SocialUser(); const profile = this.auth2.currentUser.get().getBasicProfile(); const authResponse = this.auth2.currentUser.get().getAuthResponse(true); // get complete authResponse object user.id = profile.getId(); user.name = profile.getName(); user.email = profile.getEmail(); user.photoUrl = profile.getImageUrl(); user.firstName = profile.getGivenName(); user.lastName = profile.getFamilyName(); user.response = authResponse; const resolveUser = authResponse => { user.authToken = authResponse.access_token; user.idToken = authResponse.id_token; resolve(user); }; if (options.refreshToken) { this.auth2.currentUser.get().reloadAuthResponse().then(resolveUser); } else { const authResponse = this.auth2.currentUser.get().getAuthResponse(true); resolveUser(authResponse); } } else { reject(`No user is currently logged in with ${GoogleLoginProvider.PROVIDER_ID}`); } }); } signIn(signInOptions) { const options = Object.assign(Object.assign({}, this.initOptions), signInOptions); return new Promise((resolve, reject) => { const offlineAccess = options && options.offline_access; let promise = !offlineAccess ? this.auth2.signIn(signInOptions) : this.auth2.grantOfflineAccess(signInOptions); promise .then((response) => { let user = new SocialUser(); if (response && response.code) { user.authorizationCode = response.code; } else { let profile = this.auth2.currentUser.get().getBasicProfile(); let authResponse = this.auth2.currentUser.get().getAuthResponse(true); let token = authResponse.access_token; let backendToken = authResponse.id_token; user.id = profile.getId(); user.name = profile.getName(); user.email = profile.getEmail(); user.photoUrl = profile.getImageUrl(); user.firstName = profile.getGivenName(); user.lastName = profile.getFamilyName(); user.authToken = token; user.idToken = backendToken; user.response = authResponse; } resolve(user); }, (closed) => { reject(closed); }) .catch((err) => { reject(err); }); }); } signOut(revoke) { return new Promise((resolve, reject) => { let signOutPromise; if (revoke) { signOutPromise = this.auth2.disconnect(); } else { signOutPromise = this.auth2.signOut(); } signOutPromise .then((err) => { if (err) { reject(err); } else { resolve(); } }) .catch((err) => { reject(err); }); }); } } GoogleLoginProvider.PROVIDER_ID = 'GOOGLE'; /** * The service encapsulating the social login functionality. Exposes methods like * `signIn`, `signOut`. Also, exposes an `authState` `Observable` that one can * subscribe to get the current logged in user information. * * @dynamic */ class SocialAuthService { /** * @param config A `SocialAuthServiceConfig` object or a `Promise` that resolves to a `SocialAuthServiceConfig` object */ constructor(config) { this.providers = new Map(); this.autoLogin = false; this._user = null; this._authState = new ReplaySubject(1); /* Consider making this an enum comprising LOADING, LOADED, FAILED etc. */ this.initialized = false; this._initState = new AsyncSubject(); if (config instanceof Promise) { config.then((config) => { this.initialize(config); }); } else { this.initialize(config); } } /** An `Observable` that one can subscribe to get the current logged in user information */ get authState() { return this._authState.asObservable(); } /** An `Observable` to communicate the readiness of the service and associated login providers */ get initState() { return this._initState.asObservable(); } initialize(config) { this.autoLogin = config.autoLogin !== undefined ? config.autoLogin : false; const { onError = console.error } = config; config.providers.forEach((item) => { this.providers.set(item.id, item.provider); }); Promise.all(Array.from(this.providers.values()).map((provider) => provider.initialize())) .then(() => { if (this.autoLogin) { const loginStatusPromises = []; let loggedIn = false; this.providers.forEach((provider, key) => { let promise = provider.getLoginStatus(); loginStatusPromises.push(promise); promise .then((user) => { user.provider = key; this._user = user; this._authState.next(user); loggedIn = true; }) .catch(console.debug); }); Promise.all(loginStatusPromises).catch(() => { if (!loggedIn) { this._user = null; this._authState.next(null); } }); } }) .catch((error) => { onError(error); }) .finally(() => { this.initialized = true; this._initState.next(this.initialized); this._initState.complete(); }); } refreshAuthToken(providerId) { return new Promise((resolve, reject) => { if (!this.initialized) { reject(SocialAuthService.ERR_NOT_INITIALIZED); } else if (providerId !== GoogleLoginProvider.PROVIDER_ID) { reject(SocialAuthService.ERR_NOT_SUPPORTED_FOR_REFRESH_TOKEN); } else { const providerObject = this.providers.get(providerId); if (providerObject) { providerObject .getLoginStatus({ refreshToken: true }) .then((user) => { user.provider = providerId; this._user = user; this._authState.next(user); resolve(); }) .catch((err) => { reject(err); }); } else { reject(SocialAuthService.ERR_LOGIN_PROVIDER_NOT_FOUND); } } }); } /** * A method used to sign in a user with a specific `LoginProvider`. * * @param providerId Id with which the `LoginProvider` has been registered with the service * @param signInOptions Optional `LoginProvider` specific arguments * @returns A `Promise` that resolves to the authenticated user information */ signIn(providerId, signInOptions) { return new Promise((resolve, reject) => { if (!this.initialized) { reject(SocialAuthService.ERR_NOT_INITIALIZED); } else { let providerObject = this.providers.get(providerId); if (providerObject) { providerObject .signIn(signInOptions) .then((user) => { user.provider = providerId; resolve(user); this._user = user; this._authState.next(user); }) .catch((err) => { reject(err); }); } else { reject(SocialAuthService.ERR_LOGIN_PROVIDER_NOT_FOUND); } } }); } /** * A method used to sign out the currently loggen in user. * * @param revoke Optional parameter to specify whether a hard sign out is to be performed * @returns A `Promise` that resolves if the operation is successful, rejects otherwise */ signOut(revoke = false) { return new Promise((resolve, reject) => { if (!this.initialized) { reject(SocialAuthService.ERR_NOT_INITIALIZED); } else if (!this._user) { reject(SocialAuthService.ERR_NOT_LOGGED_IN); } else { let providerId = this._user.provider; let providerObject = this.providers.get(providerId); if (providerObject) { providerObject .signOut(revoke) .then(() => { resolve(); this._user = null; this._authState.next(null); }) .catch((err) => { reject(err); }); } else { reject(SocialAuthService.ERR_LOGIN_PROVIDER_NOT_FOUND); } } }); } } SocialAuthService.ERR_LOGIN_PROVIDER_NOT_FOUND = 'Login provider not found'; SocialAuthService.ERR_NOT_LOGGED_IN = 'Not logged in'; SocialAuthService.ERR_NOT_INITIALIZED = 'Login providers not ready yet. Are there errors on your console?'; SocialAuthService.ERR_NOT_SUPPORTED_FOR_REFRESH_TOKEN = 'Chosen login provider is not supported for refreshing a token'; SocialAuthService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.1.3", ngImport: i0, type: SocialAuthService, deps: [{ token: 'SocialAuthServiceConfig' }], target: i0.ɵɵFactoryTarget.Injectable }); SocialAuthService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.1.3", ngImport: i0, type: SocialAuthService }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.1.3", ngImport: i0, type: SocialAuthService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: undefined, decorators: [{ type: Inject, args: ['SocialAuthServiceConfig'] }] }]; } }); /** * The main module of angularx-social-login library. */ class SocialLoginModule { constructor(parentModule) { if (parentModule) { throw new Error('SocialLoginModule is already loaded. Import it in the AppModule only'); } } static initialize(config) { return { ngModule: SocialLoginModule, providers: [ SocialAuthService, { provide: 'SocialAuthServiceConfig', useValue: config } ] }; } } SocialLoginModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.1.3", ngImport: i0, type: SocialLoginModule, deps: [{ token: SocialLoginModule, optional: true, skipSelf: true }], target: i0.ɵɵFactoryTarget.NgModule }); SocialLoginModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.1.3", ngImport: i0, type: SocialLoginModule, imports: [CommonModule] }); SocialLoginModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.1.3", ngImport: i0, type: SocialLoginModule, providers: [ SocialAuthService ], imports: [[ CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.1.3", ngImport: i0, type: SocialLoginModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule ], providers: [ SocialAuthService ] }] }], ctorParameters: function () { return [{ type: SocialLoginModule, decorators: [{ type: Optional }, { type: SkipSelf }] }]; } }); // Simulates login / logout without actually requiring an Internet connection. // // Useful for certain development situations. // // For example, if you want to simulate the greatest football referee England has ever produced: // // const dummyUser: SocialUser = { // id: '0123456789', // name: 'Howard Webb', // email: 'howard@webb.com', // firstName: 'Howard', // lastName: 'Webb', // authToken: 'dummyAuthToken', // photoUrl: 'https://en.wikipedia.org/wiki/Howard_Webb#/media/File:Howard_Webb_march11.jpg', // provider: 'DUMMY', // idToken: 'dummyIdToken', // authorizationCode: 'dummyAuthCode' // }; // // let config = new AuthServiceConfig([ // { ... }, // { // id: DummyLoginProvider.PROVIDER_ID, // provider: new DummyLoginProvider(dummyUser) // Pass your user into the constructor // }, // { ... } // ]); class DummyLoginProvider extends BaseLoginProvider { constructor(dummy) { super(); if (dummy) { this.dummy = dummy; } else { this.dummy = DummyLoginProvider.DEFAULT_USER; } // Start not logged in this.loggedIn = false; } getLoginStatus() { return new Promise((resolve, reject) => { if (this.loggedIn) { resolve(this.dummy); } else { reject('No user is currently logged in.'); } }); } initialize() { return new Promise((resolve, reject) => { resolve(); }); } signIn() { return new Promise((resolve, reject) => { this.loggedIn = true; resolve(this.dummy); }); } signOut(revoke) { return new Promise((resolve, reject) => { this.loggedIn = false; resolve(); }); } } DummyLoginProvider.PROVIDER_ID = 'DUMMY'; DummyLoginProvider.DEFAULT_USER = { id: '1234567890', name: 'Mickey Mouse', email: 'mickey@mouse.com', firstName: 'Mickey', lastName: 'Mouse', authToken: 'dummyAuthToken', photoUrl: 'https://en.wikipedia.org/wiki/File:Mickey_Mouse.png', provider: 'DUMMY', idToken: 'dummyIdToken', authorizationCode: 'dummyAuthCode', response: {}, }; class FacebookLoginProvider extends BaseLoginProvider { constructor(clientId, initOptions = {}) { super(); this.clientId = clientId; this.requestOptions = { scope: 'email,public_profile', locale: 'en_US', fields: 'name,email,picture,first_name,last_name', version: 'v10.0', }; this.requestOptions = Object.assign(Object.assign({}, this.requestOptions), initOptions); } initialize() { return new Promise((resolve, reject) => { try { this.loadScript(FacebookLoginProvider.PROVIDER_ID, `//connect.facebook.net/${this.requestOptions.locale}/sdk.js`, () => { FB.init({ appId: this.clientId, autoLogAppEvents: true, cookie: true, xfbml: true, version: this.requestOptions.version, }); resolve(); }); } catch (err) { reject(err); } }); } getLoginStatus() { return new Promise((resolve, reject) => { FB.getLoginStatus((response) => { if (response.status === 'connected') { let authResponse = response.authResponse; FB.api(`/me?fields=${this.requestOptions.fields}`, (fbUser) => { let user = new SocialUser(); user.id = fbUser.id; user.name = fbUser.name; user.email = fbUser.email; user.photoUrl = 'https://graph.facebook.com/' + fbUser.id + '/picture?type=normal&access_token=' + authResponse.accessToken; user.firstName = fbUser.first_name; user.lastName = fbUser.last_name; user.authToken = authResponse.accessToken; user.response = fbUser; resolve(user); }); } else { reject(`No user is currently logged in with ${FacebookLoginProvider.PROVIDER_ID}`); } }); }); } signIn(signInOptions) { const options = Object.assign(Object.assign({}, this.requestOptions), signInOptions); return new Promise((resolve, reject) => { FB.login((response) => { if (response.authResponse) { let authResponse = response.authResponse; FB.api(`/me?fields=${options.fields}`, (fbUser) => { let user = new SocialUser(); user.id = fbUser.id; user.name = fbUser.name; user.email = fbUser.email; user.photoUrl = 'https://graph.facebook.com/' + fbUser.id + '/picture?type=normal'; user.firstName = fbUser.first_name; user.lastName = fbUser.last_name; user.authToken = authResponse.accessToken; user.response = fbUser; resolve(user); }); } else { reject('User cancelled login or did not fully authorize.'); } }, options); }); } signOut() { return new Promise((resolve, reject) => { FB.logout((response) => { resolve(); }); }); } } FacebookLoginProvider.PROVIDER_ID = 'FACEBOOK'; class AmazonLoginProvider extends BaseLoginProvider { constructor(clientId, initOptions = { scope: 'profile', scope_data: { profile: { essential: false }, }, redirect_uri: location.origin, }) { super(); this.clientId = clientId; this.initOptions = initOptions; } initialize() { let amazonRoot = null; if (document) { amazonRoot = document.createElement('div'); amazonRoot.id = 'amazon-root'; document.body.appendChild(amazonRoot); } window.onAmazonLoginReady = () => { amazon.Login.setClientId(this.clientId); }; return new Promise((resolve, reject) => { try { this.loadScript('amazon-login-sdk', 'https://assets.loginwithamazon.com/sdk/na/login1.js', () => { resolve(); }, amazonRoot); } catch (err) { reject(err); } }); } getLoginStatus() { return new Promise((resolve, reject) => { let token = this.retrieveToken(); if (token) { amazon.Login.retrieveProfile(token, (response) => { if (response.success) { let user = new SocialUser(); user.id = response.profile.CustomerId; user.name = response.profile.Name; user.email = response.profile.PrimaryEmail; user.response = response.profile; resolve(user); } else { reject(response.error); } }); } else { reject(`No user is currently logged in with ${AmazonLoginProvider.PROVIDER_ID}`); } }); } signIn(signInOptions) { const options = Object.assign(Object.assign({}, this.initOptions), signInOptions); return new Promise((resolve, reject) => { amazon.Login.authorize(options, (authResponse) => { if (authResponse.error) { reject(authResponse.error); } else { amazon.Login.retrieveProfile(authResponse.access_token, (response) => { let user = new SocialUser(); user.id = response.profile.CustomerId; user.name = response.profile.Name; user.email = response.profile.PrimaryEmail; user.authToken = authResponse.access_token; user.response = response.profile; this.persistToken(authResponse.access_token); resolve(user); }); } }); }); } signOut() { return new Promise((resolve, reject) => { try { amazon.Login.logout(); this.clearToken(); resolve(); } catch (err) { reject(err.message); } }); } persistToken(token) { localStorage.setItem(`${AmazonLoginProvider.PROVIDER_ID}_token`, token); } retrieveToken() { return localStorage.getItem(`${AmazonLoginProvider.PROVIDER_ID}_token`); } clearToken() { localStorage.removeItem(`${AmazonLoginProvider.PROVIDER_ID}_token`); } } AmazonLoginProvider.PROVIDER_ID = 'AMAZON'; class VKLoginProvider extends BaseLoginProvider { constructor(clientId, initOptions = { fields: 'photo_max,contacts', version: '5.124', }) { super(); this.clientId = clientId; this.initOptions = initOptions; this.VK_API_URL = '//vk.com/js/api/openapi.js'; this.VK_API_GET_USER = 'users.get'; } initialize() { return new Promise((resolve, reject) => { try { this.loadScript(VKLoginProvider.PROVIDER_ID, this.VK_API_URL, () => { VK.init({ apiId: this.clientId, }); resolve(); }); } catch (err) { reject(err); } }); } getLoginStatus() { return new Promise((resolve, reject) => this.getLoginStatusInternal(resolve, reject)); } signIn() { return new Promise((resolve, reject) => this.signInInternal(resolve, reject)); } signOut() { return new Promise((resolve, reject) => { VK.Auth.logout((response) => { resolve(); }); }); } signInInternal(resolve, reject) { VK.Auth.login((loginResponse) => { if (loginResponse.status === 'connected') { this.getUser(loginResponse.session.mid, loginResponse.session.sid, resolve); } }); } getUser(userId, token, resolve) { VK.Api.call(this.VK_API_GET_USER, { user_id: userId, fields: this.initOptions.fields, v: this.initOptions.version, }, (userResponse) => { resolve(this.createUser(Object.assign({}, { token }, userResponse.response[0]))); }); } getLoginStatusInternal(resolve, reject) { VK.Auth.getLoginStatus((loginResponse) => { if (loginResponse.status === 'connected') { this.getUser(loginResponse.session.mid, loginResponse.session.sid, resolve); } }); } createUser(response) { const user = new SocialUser(); user.id = response.id; user.name = `${response.first_name} ${response.last_name}`; user.photoUrl = response.photo_max; user.authToken = response.token; return user; } } VKLoginProvider.PROVIDER_ID = 'VK'; /** * Protocol modes supported by MSAL. */ var ProtocolMode; (function (ProtocolMode) { ProtocolMode["AAD"] = "AAD"; ProtocolMode["OIDC"] = "OIDC"; })(ProtocolMode || (ProtocolMode = {})); const COMMON_AUTHORITY = 'https://login.microsoftonline.com/common/'; /** * Microsoft Authentication using MSAL v2: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser */ class MicrosoftLoginProvider extends BaseLoginProvider { constructor(clientId, initOptions) { super(); this.clientId = clientId; this.initOptions = { authority: COMMON_AUTHORITY, scopes: ['openid', 'email', 'profile', 'User.Read'], knownAuthorities: [], protocolMode: ProtocolMode.AAD, clientCapabilities: [], cacheLocation: 'sessionStorage' }; this.initOptions = Object.assign(Object.assign({}, this.initOptions), initOptions); } initialize() { return new Promise((resolve, reject) => { this.loadScript(MicrosoftLoginProvider.PROVIDER_ID, 'https://alcdn.msauth.net/browser/2.13.1/js/msal-browser.js', () => { var _a; try { const config = { auth: { clientId: this.clientId, redirectUri: (_a = this.initOptions.redirect_uri) !== null && _a !== void 0 ? _a : location.origin, authority: this.initOptions.authority, knownAuthorities: this.initOptions.knownAuthorities, protocolMode: this.initOptions.protocolMode, clientCapabilities: this.initOptions.clientCapabilities }, cache: !this.initOptions.cacheLocation ? null : { cacheLocation: this.initOptions.cacheLocation } }; this._instance = new msal.PublicClientApplication(config); resolve(); } catch (e) { reject(e); } }); }); } getSocialUser(loginResponse) { return new Promise((resolve, reject) => { //After login, use Microsoft Graph API to get user info let meRequest = new XMLHttpRequest(); meRequest.onreadystatechange = () => { if (meRequest.readyState == 4) { try { if (meRequest.status == 200) { let userInfo = JSON.parse(meRequest.responseText); let user = new SocialUser(); user.provider = MicrosoftLoginProvider.PROVIDER_ID; user.id = loginResponse.idToken; user.authToken = loginResponse.accessToken; user.name = loginResponse.idTokenClaims.name; user.email = loginResponse.account.username; user.idToken = loginResponse.idToken; user.response = loginResponse; user.firstName = userInfo.givenName; user.lastName = userInfo.surname; resolve(user); } else { reject(`Error retrieving user info: ${meRequest.status}`); } } catch (err) { reject(err); } } }; //Microsoft Graph ME Endpoint: https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http meRequest.open('GET', 'https://graph.microsoft.com/v1.0/me'); meRequest.setRequestHeader('Authorization', `Bearer ${loginResponse.accessToken}`); try { meRequest.send(); } catch (err) { reject(err); } }); } getLoginStatus() { return __awaiter(this, void 0, void 0, function* () { const accounts = this._instance.getAllAccounts(); if ((accounts === null || accounts === void 0 ? void 0 : accounts.length) > 0) { const loginResponse = yield this._instance.ssoSilent({ scopes: this.initOptions.scopes, loginHint: accounts[0].username }); return yield this.getSocialUser(loginResponse); } else { throw `No user is currently logged in with ${MicrosoftLoginProvider.PROVIDER_ID}`; } }); } signIn() { return __awaiter(this, void 0, void 0, function* () { const loginResponse = yield this._instance.loginPopup({ scopes: this.initOptions.scopes, prompt: this.initOptions.prompt, }); return yield this.getSocialUser(loginResponse); }); } signOut(revoke) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { const accounts = this._instance.getAllAccounts(); if ((accounts === null || accounts === void 0 ? void 0 : accounts.length) > 0) { yield this._instance.logoutPopup({ account: accounts[0], postLogoutRedirectUri: (_b = (_a = this.initOptions.logout_redirect_uri) !== null && _a !== void 0 ? _a : this.initOptions.redirect_uri) !== null && _b !== void 0 ? _b : location.href }); } }); } } MicrosoftLoginProvider.PROVIDER_ID = 'MICROSOFT'; /** * Generated bundle index. Do not edit. */ export { AmazonLoginProvider, BaseLoginProvider, DummyLoginProvider, FacebookLoginProvider, GoogleLoginProvider, MicrosoftLoginProvider, SocialAuthService, SocialLoginModule, SocialUser, VKLoginProvider }; //# sourceMappingURL=slr-angularx-social-login.mjs.map