@qelos/auth
Version:
Express Passport authentication service
199 lines (198 loc) • 10.3 kB
JavaScript
;
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;