UNPKG

@qelos/auth

Version:

Express Passport authentication service

199 lines (198 loc) 10.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.authCallbackFromLinkedIn = exports.loginWithLinkedIn = exports.getLinkedinSource = void 0; const user_1 = __importDefault(require("../../models/user")); const integration_source_1 = require("../../services/integration-source"); const req_host_1 = require("../../services/req-host"); const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); const users_1 = require("../../services/users"); const tokens_1 = require("../../services/tokens"); const config_1 = require("../../../config"); const encrypted_data_1 = require("../../services/encrypted-data"); const api_kit_1 = require("@qelos/api-kit"); const workspace_configuration_1 = require("../../services/workspace-configuration"); const workspaces_1 = require("../../services/workspaces"); const assets_service_api_1 = require("../../services/assets-service-api"); const logger_1 = __importDefault(require("../../services/logger")); const LINKEDIN_AUTH_URL = 'https://www.linkedin.com/oauth/v2/authorization'; const LINKEDIN_TOKEN_URL = 'https://www.linkedin.com/oauth/v2/accessToken'; function getLinkedinSource(req, res, next) { var _a; return __awaiter(this, void 0, void 0, function* () { if (!((_a = req.authConfig.socialLoginsSources) === null || _a === void 0 ? void 0 : _a.linkedin)) { res.status(400).json({ message: 'LinkedIn social login does not exist' }).end(); return; } const source = yield (0, integration_source_1.getIntegrationSource)(req.headers.tenant, req.authConfig.socialLoginsSources.linkedin); if (!source) { res.status(400).json({ message: 'LinkedIn social login is not enabled' }).end(); return; } req.source = source; next(); }); } exports.getLinkedinSource = getLinkedinSource; function getLinkedInRedirectUri(tenantHost) { const fullTenantHost = tenantHost.startsWith('http://') || tenantHost.startsWith('https://') ? tenantHost : `https://${tenantHost}`; return `${fullTenantHost}/api/auth/linkedin/callback`; } function loginWithLinkedIn(req, res) { return __awaiter(this, void 0, void 0, function* () { const { clientId, scope } = req.source.metadata; const redirectUri = getLinkedInRedirectUri(req.headers.tenanthost); const linkedinAuthUrl = `${LINKEDIN_AUTH_URL}?response_type=code&client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}`; res.redirect(linkedinAuthUrl); }); } exports.loginWithLinkedIn = loginWithLinkedIn; function authCallbackFromLinkedIn(req, res) { var _a, _b, _c, _d, _e; return __awaiter(this, void 0, void 0, function* () { const { clientId } = req.source.metadata; const { clientSecret } = req.source.authentication; const authCode = Array.isArray(req.query.code) ? req.query.code[0] : req.query.code; if (!authCode || typeof authCode !== 'string') { return res.status(400).json({ message: 'Invalid authorization code' }); } const redirectUri = getLinkedInRedirectUri(req.headers.tenanthost); try { // Exchange the authorization code for an access token and ID token const tokenResponse = yield fetch(LINKEDIN_TOKEN_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code: authCode, redirect_uri: redirectUri, client_id: clientId, client_secret: clientSecret, }).toString(), }); const tokenData = yield tokenResponse.json(); if (!tokenResponse.ok) { res.status(tokenResponse.status).json({ message: 'Failed to get access token', details: tokenData }).end(); return; } if (!tokenData.access_token) { res.status(400).json({ message: 'Failed to get access token' }).end(); return; } if (!tokenData.id_token) { res.status(400).json({ message: 'ID token is missing in the response' }).end(); return; } let userData; if (tokenData.id_token) { userData = jsonwebtoken_1.default.decode(tokenData.id_token); } let user; try { user = yield (0, users_1.getUser)({ username: userData.email, tenant: req.headers.tenant }); user.email = userData.email; if (userData.picture) { try { user.profileImage = yield (0, assets_service_api_1.uploadProfileImage)(req.headers.tenant, user._id, userData.picture); } catch (error) { logger_1.default.error('failed to upload profile image', error); user.profileImage = userData.picture; // Fallback to original URL } } if (!user.emailVerified) { user.emailVerified = true; } if (!((_a = user.socialLogins) === null || _a === void 0 ? void 0 : _a.includes('linkedin'))) { user.socialLogins = user.socialLogins || []; user.socialLogins.push('linkedin'); user.markModified('socialLogins'); } yield user.save(); } catch (_f) { if (typeof req.authConfig.allowSocialAutoRegistration === 'boolean' && !req.authConfig.allowSocialAutoRegistration) { res.redirect('/login?error=needs-registration&email=' + encodeURIComponent(userData.email)); return; } const tempUserId = `profile_${Date.now()}`; let profileImage = ""; if (userData.picture) { try { profileImage = yield (0, assets_service_api_1.uploadProfileImage)(req.headers.tenant, tempUserId, userData.picture); } catch (error) { profileImage = userData.picture; } } user = new user_1.default({ tenant: req.headers.tenant, username: userData.email, email: userData.email, fullName: userData.name || '', firstName: userData.given_name || '', lastName: userData.family_name || '', profileImage, emailVerified: true, socialLogins: ['linkedin'], }); yield user.save(); (0, api_kit_1.emitPlatformEvent)({ tenant: req.headers.tenant, user: user.id, source: 'auth', kind: 'signup', eventName: 'user-registered', description: 'User registered via LinkedIn', metadata: { user: { tenant: user.tenant, username: user.username, email: user.email, fullName: user.fullName, firstName: user.firstName, lastName: user.lastName, profileImage: user.profileImage, roles: user.roles, _id: user._id, created: user.created, }, source: 'linkedin', }, }); } yield (0, encrypted_data_1.setEncryptedData)(req.headers.tenant, `${user.id}-linkedinToken`, JSON.stringify(tokenData)); let workspace; try { const wsConfig = yield (0, workspace_configuration_1.getWorkspaceConfiguration)(req.headers.tenant); if (wsConfig.isActive) { workspace = yield (0, workspaces_1.getWorkspaceForUser)(req.headers.tenant, user._id, ((_b = user.lastLogin) === null || _b === void 0 ? void 0 : _b.workspace) || ((_e = (_d = (_c = user.tokens) === null || _c === void 0 ? void 0 : _c.at(-1)) === null || _d === void 0 ? void 0 : _d.metadata) === null || _e === void 0 ? void 0 : _e.workspace)); } } catch (_g) { logger_1.default.log('Error getting workspace in linkedin login'); } const requestHost = (0, req_host_1.getRequestHost)(req); const { token: newToken } = (0, tokens_1.getSignedToken)(user, workspace, (0, tokens_1.getUniqueId)(), String(config_1.cookieTokenExpiration / 1000)); (0, tokens_1.setCookie)(res, (0, users_1.getCookieTokenName)(user.tenant), newToken, null, requestHost); res.redirect('/'); } catch (error) { return res.status(500).json({ message: 'Internal server error' }); } }); } exports.authCallbackFromLinkedIn = authCallbackFromLinkedIn;