@gsb-core/core
Version:
GSB core services and classes for platform-independent web applications
638 lines • 25.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthService = void 0;
const gsb_entity_service_1 = require("../entity/gsb-entity.service");
const gsb_config_1 = require("../../config/gsb-config");
const gsb_cache_service_1 = require("../cache/gsb-cache.service");
// Storage key prefixes
const TOKEN_PREFIX = 'gsb_auth_';
const USER_DATA_PREFIX = 'gsb_user_';
const ACTIVE_TENANT_KEY = 'gsb_active_tenant';
// Role constants
const ADMIN_ROLE_ID = '59b62a77-b722-44ad-9a2d-478365e8f1a0';
// Cache keys
const CACHE_PREFIX = 'auth_';
// Token refresh settings
const TOKEN_REFRESH_BUFFER = 3 * 60 * 1000; // 3 minutes before expiry
const TOKEN_REFRESH_RETRY_DELAY = 30 * 1000; // 30 seconds between retries
const TOKEN_REFRESH_MAX_RETRIES = 3; // Maximum number of retry attempts
// Singleton instance
let authServiceInstance = null;
/**
* Authentication service for managing auth tokens with GSB backend
* Single-tenant implementation with forward compatibility for multi-tenant
*/
class AuthService {
constructor() {
this.token = null;
this.refreshTokenTimeout = null;
this.refreshTokenRetryCount = 0;
this.gsbService = gsb_entity_service_1.GsbEntityService.getInstance();
this.cacheService = gsb_cache_service_1.GsbCacheService.getInstance();
this.tenantCode = (0, gsb_config_1.getGsbTenantCode)();
this.initFromStorage();
}
static getInstance() {
if (!authServiceInstance) {
authServiceInstance = new AuthService();
}
return authServiceInstance;
}
getKeys(tenantCode, keyType) {
let prefix;
let cachePrefix;
switch (keyType) {
case 'token':
prefix = TOKEN_PREFIX;
cachePrefix = `${CACHE_PREFIX}token_`;
break;
case 'userData':
prefix = USER_DATA_PREFIX;
cachePrefix = `${CACHE_PREFIX}userData_`;
break;
case 'expiry':
prefix = `${TOKEN_PREFIX}expiry_`;
cachePrefix = `${CACHE_PREFIX}expiry_`;
break;
}
return {
storageKey: `${prefix}${tenantCode}`,
cacheKey: `${cachePrefix}${tenantCode}`
};
}
getCacheValue(tenantCode, keyType) {
const { cacheKey, storageKey } = this.getKeys(tenantCode, keyType);
return this.cacheService.getCache(cacheKey, storageKey);
}
setCacheValue(tenantCode, keyType, value, duration) {
const { cacheKey, storageKey } = this.getKeys(tenantCode, keyType);
this.cacheService.setCache(cacheKey, value, duration, storageKey);
}
removeCacheValue(tenantCode, keyType) {
const { cacheKey, storageKey } = this.getKeys(tenantCode, keyType);
this.cacheService.removeCache(cacheKey, storageKey);
}
saveToStorage(token, userData, remember) {
// Always update memory state
this.token = token;
this.tenantCode = this.getTenantCode();
if (!this.tenantCode) {
return;
}
if (typeof window !== 'undefined') {
try {
// Save active tenant
this.cacheService.setCache('active_tenant', this.tenantCode, undefined, ACTIVE_TENANT_KEY);
// Calculate cache duration based on token expiry
let cacheDuration;
let tokenExpiryTime = null;
// First, try to get expiry from userData
if (userData === null || userData === void 0 ? void 0 : userData.expireDate) {
tokenExpiryTime = new Date(userData.expireDate).getTime();
}
else {
// If not in userData, try to decode from JWT token
try {
const tokenPayload = this.getUserFromToken(token);
if (tokenPayload === null || tokenPayload === void 0 ? void 0 : tokenPayload.exp) {
// JWT exp is in seconds, convert to milliseconds
tokenExpiryTime = tokenPayload.exp * 1000;
}
}
catch (error) {
console.warn('Could not decode token expiry:', error);
}
}
if (tokenExpiryTime) {
const now = Date.now();
const tokenDuration = Math.max(0, tokenExpiryTime - now);
// Use the token's actual expiry time
cacheDuration = tokenDuration;
}
else {
// Fallback to hardcoded durations only if no expiry found
cacheDuration = remember ? 30 * 24 * 60 * 60 * 1000 : 24 * 60 * 60 * 1000;
}
// Store token and user data in cache
this.setCacheValue(this.tenantCode, 'token', token, cacheDuration);
this.setCacheValue(this.tenantCode, 'userData', userData, cacheDuration);
// Set cookies for middleware to use
if (typeof document !== 'undefined') {
let cookieExpiry;
if (remember) {
if (tokenExpiryTime) {
// Use actual token expiry for remember me
cookieExpiry = new Date(tokenExpiryTime);
}
else {
// Fallback to cache duration if no token expiry
cookieExpiry = new Date(Date.now() + cacheDuration);
}
}
// For session (non-remember), cookieExpiry remains undefined (session cookie)
const tokenKeys = this.getKeys(this.tenantCode, 'token');
document.cookie = `${tokenKeys.storageKey}=${token}; path=/; ${cookieExpiry ? `expires=${cookieExpiry.toUTCString()};` : ''} SameSite=Lax`;
// Store token expiry for validation
if (userData === null || userData === void 0 ? void 0 : userData.expireDate) {
const expiryKeys = this.getKeys(this.tenantCode, 'expiry');
document.cookie = `${expiryKeys.storageKey}=${userData.expireDate}; path=/; ${cookieExpiry ? `expires=${cookieExpiry.toUTCString()};` : ''} SameSite=Lax`;
// Cache expiry date
this.setCacheValue(this.tenantCode, 'expiry', userData.expireDate, cacheDuration);
}
}
// Setup token refresh for session-based auth (non-remember me)
if (!remember && (userData === null || userData === void 0 ? void 0 : userData.expireDate)) {
this.scheduleTokenRefresh(userData.expireDate);
}
else {
this.clearTokenRefresh();
}
}
catch (error) {
console.error('Error saving auth data to storage:', error);
}
}
}
scheduleTokenRefresh(expireDate) {
// Clear any existing refresh timeout
this.clearTokenRefresh();
this.refreshTokenRetryCount = 0;
// Only setup refresh in browser environment
if (typeof window !== 'undefined') {
try {
const expiryTime = new Date(expireDate).getTime();
const now = Date.now();
// Calculate time until refresh (3 minutes before expiry)
const timeUntilRefresh = Math.max(0, expiryTime - now - TOKEN_REFRESH_BUFFER);
if (timeUntilRefresh > 0) {
this.refreshTokenTimeout = setTimeout(() => {
this.performTokenRefresh();
}, timeUntilRefresh);
}
else {
// Token is already close to expiring or expired, refresh immediately
this.performTokenRefresh();
}
}
catch (error) {
console.error('Error scheduling token refresh:', error);
}
}
}
performTokenRefresh() {
this.refreshToken()
.then(response => {
if (response.success) {
// Reset retry count on success
this.refreshTokenRetryCount = 0;
console.log('Token refreshed successfully');
}
else {
this.handleRefreshFailure();
}
})
.catch(() => {
this.handleRefreshFailure();
});
}
handleRefreshFailure() {
this.refreshTokenRetryCount++;
if (this.refreshTokenRetryCount <= TOKEN_REFRESH_MAX_RETRIES) {
console.log(`Token refresh failed. Retrying (${this.refreshTokenRetryCount}/${TOKEN_REFRESH_MAX_RETRIES}) in ${TOKEN_REFRESH_RETRY_DELAY / 1000}s`);
// Schedule retry
this.refreshTokenTimeout = setTimeout(() => {
this.performTokenRefresh();
}, TOKEN_REFRESH_RETRY_DELAY);
}
else {
console.error('Token refresh failed after maximum retry attempts');
// Reset retry count for next time
this.refreshTokenRetryCount = 0;
}
}
clearTokenRefresh() {
if (this.refreshTokenTimeout) {
clearTimeout(this.refreshTokenTimeout);
this.refreshTokenTimeout = null;
}
}
clearStorage(allTenants = false) {
// Clear token refresh
this.clearTokenRefresh();
if (typeof window !== 'undefined') {
try {
if (allTenants) {
// Get all available tenants
const availableTenants = this.getAvailableTenants();
// Clear data for each tenant
availableTenants.forEach(tenant => {
// Remove from cache
this.removeCacheValue(tenant, 'token');
this.removeCacheValue(tenant, 'userData');
this.removeCacheValue(tenant, 'expiry');
// Clear cookies
if (typeof document !== 'undefined') {
const tokenKeys = this.getKeys(tenant, 'token');
const expiryKeys = this.getKeys(tenant, 'expiry');
document.cookie = `${tokenKeys.storageKey}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
document.cookie = `${expiryKeys.storageKey}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
}
});
// Remove active tenant
this.cacheService.removeCache('active_tenant', ACTIVE_TENANT_KEY);
}
else {
let tenantCode = this.getTenantCode();
if (!tenantCode) {
return;
}
// Clear only current tenant data
this.removeCacheValue(tenantCode, 'token');
this.removeCacheValue(tenantCode, 'userData');
this.removeCacheValue(tenantCode, 'expiry');
// Clear cookies
if (typeof document !== 'undefined') {
const tokenKeys = this.getKeys(tenantCode, 'token');
const expiryKeys = this.getKeys(tenantCode, 'expiry');
document.cookie = `${tokenKeys.storageKey}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
document.cookie = `${expiryKeys.storageKey}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
}
}
}
catch (error) {
console.error('Error clearing storage:', error);
}
}
}
/**
* Initialize auth state from storage if available
*/
initFromStorage() {
this.tenantCode = this.getTenantCode();
if (!this.tenantCode) {
return;
}
if (typeof window !== 'undefined') {
try {
// Get the active tenant from cache or use the current tenant code
const activeTenant = this.cacheService.getCache('active_tenant', ACTIVE_TENANT_KEY) || this.tenantCode;
// If tenant changed, update internal state
if (activeTenant !== this.tenantCode) {
this.tenantCode = activeTenant;
}
// Try to get token from cache
const token = this.getCacheValue(this.tenantCode, 'token');
if (token) {
this.token = token;
}
}
catch (error) {
console.error('Error initializing from storage:', error);
}
}
}
/**
* Refresh the authentication token
* @returns Promise with the refresh response
*/
async refreshToken() {
var _a;
try {
if (!this.token) {
return {
success: false,
error: 'No token to refresh'
};
}
const response = await this.gsbService.refreshToken({}, this.token, this.tenantCode);
if ((_a = response === null || response === void 0 ? void 0 : response.auth) === null || _a === void 0 ? void 0 : _a.token) {
const token = response.auth.token;
// Get current user data
const userData = this.getCurrentUser() || {};
// Update expiry date if provided
if (response.auth.expireDate) {
userData.expireDate = response.auth.expireDate;
}
// Save with the same remember setting (false for auto-refresh)
this.saveToStorage(token, userData, false);
return {
success: true,
token,
userData
};
}
else {
return {
success: false,
error: 'Token refresh failed: Invalid response'
};
}
}
catch (error) {
console.error('Token refresh error:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Token refresh failed'
};
}
}
/**
* Check if user is authenticated
*/
isAuthenticated() {
const token = this.getToken();
if (!token) {
return false;
}
try {
const userData = this.getCurrentUser();
if (!userData || !userData.expireDate) {
return false;
}
const expiryDate = new Date(userData.expireDate);
const now = new Date();
return expiryDate > now;
}
catch (error) {
console.error('Error checking token expiry:', error);
return false;
}
}
/**
* Authenticate a user with GSB
*/
async login(credentials) {
var _a, _b;
try {
if (credentials.remember === undefined) {
credentials.remember = true;
}
credentials.includeUserInfo = true;
if (!((_a = credentials === null || credentials === void 0 ? void 0 : credentials.variation) === null || _a === void 0 ? void 0 : _a.tenantCode)) {
if (!(credentials === null || credentials === void 0 ? void 0 : credentials.variation)) {
credentials.variation = {};
}
credentials.variation.tenantCode = this.tenantCode;
}
const response = await this.gsbService.getToken(credentials);
if ((_b = response === null || response === void 0 ? void 0 : response.auth) === null || _b === void 0 ? void 0 : _b.token) {
const token = response.auth.token;
const userData = {
...response.auth,
roles: response.auth.roles || [],
groups: response.auth.groups || []
};
this.saveToStorage(token, userData, credentials.remember);
return {
success: true,
token,
userData
};
}
else {
return {
success: false,
error: 'Authentication failed: Invalid token response'
};
}
}
catch (error) {
console.error('Login error:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Authentication failed'
};
}
}
/**
* Log out the current user
* @param allTenants Whether to logout from all tenants (default: false)
*/
logout(allTenants = false) {
this.token = null;
this.clearTokenRefresh();
this.clearStorage(allTenants);
this.cacheService.clearCache();
(0, gsb_config_1.setGsbToken)(undefined);
}
/**
* Set working tenant for multi-tenant support
* @param tenantCode The tenant code to switch to
* @param token Optional JWT token for the tenant (if not provided, attempts to load from storage)
* @returns True if successful switch, false if failed (e.g., no token available)
*/
setWorkingTenant(tenantCode, token) {
// Clear existing refresh for previous tenant
this.clearTokenRefresh();
if (tenantCode === this.tenantCode && this.token) {
// Already on this tenant with valid token
return true;
}
// If token is provided, use it
if (token) {
this.tenantCode = tenantCode;
this.token = token;
// Save active tenant
this.cacheService.setCache('active_tenant', tenantCode, undefined, ACTIVE_TENANT_KEY);
// Cache the token
this.setCacheValue(tenantCode, 'token', token);
// Check if we need to setup token refresh for this tenant
const userData = this.getCacheValue(tenantCode, 'userData');
if (userData && userData.expireDate && this.token) {
// If expiry is less than 30 days, it's a session token (not remember me)
const expiry = new Date(userData.expireDate).getTime();
const now = Date.now();
if ((expiry - now) < 30 * 24 * 60 * 60 * 1000) {
this.scheduleTokenRefresh(userData.expireDate);
}
}
return true;
}
// Try to get from cache
const cachedToken = this.getCacheValue(tenantCode, 'token');
if (cachedToken) {
this.tenantCode = tenantCode;
this.token = cachedToken;
// Save active tenant
this.cacheService.setCache('active_tenant', tenantCode, undefined, ACTIVE_TENANT_KEY);
// Check if we need to setup token refresh for this tenant
const userData = this.getCacheValue(tenantCode, 'userData');
if (userData && userData.expireDate && this.token) {
// If expiry is less than 30 days, it's a session token (not remember me)
const expiry = new Date(userData.expireDate).getTime();
const now = Date.now();
if ((expiry - now) < 30 * 24 * 60 * 60 * 1000) {
this.scheduleTokenRefresh(userData.expireDate);
}
}
return true;
}
// No token available for this tenant
return false;
}
/**
* Get all available tenant codes from storage
* @returns Array of tenant codes that have stored tokens
*/
getAvailableTenants() {
let tenantCode = this.getTenantCode();
const availableTenants = new Set();
if (tenantCode) {
availableTenants.add(tenantCode);
}
try {
// Use cache service to find all tenants with tokens
if (typeof localStorage !== 'undefined') {
const localStorageKeys = Object.keys(localStorage);
localStorageKeys.forEach(key => {
if (key.startsWith(TOKEN_PREFIX) && !key.includes('expiry_')) {
const tenantCode = key.replace(TOKEN_PREFIX, '');
availableTenants.add(tenantCode);
}
});
}
}
catch (error) {
console.error('Error getting available tenants:', error);
}
return Array.from(availableTenants);
}
/**
* Get user information from JWT token
*/
getUserFromToken(token) {
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64)
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join(''));
return JSON.parse(jsonPayload);
}
catch (error) {
console.error('Error decoding token:', error);
return null;
}
}
/**
* Check if the current user has a specific role
*/
hasRole(role) {
if (!this.token) {
return false;
}
const userData = this.getCurrentUser();
if (!userData || !userData.roles) {
return false;
}
return userData.roles.includes(role);
}
/**
* Check if the current user has the admin role
*/
isAdmin() {
return this.hasRole(ADMIN_ROLE_ID);
}
/**
* Get the subscription tier from the current user data
*/
getSubscription() {
const userData = this.getCurrentUser();
return userData === null || userData === void 0 ? void 0 : userData.subscription;
}
/**
* Get the current tenant code
*/
getTenantCode() {
if (!this.tenantCode) {
this.tenantCode = (0, gsb_config_1.getGsbTenantCode)();
if (this.tenantCode) {
this.cacheService.setCache('tenantCode', this.tenantCode, undefined, this.tenantCode);
}
}
return this.tenantCode;
}
/**
* Get the current auth token
*/
getToken() {
let tenantCode = this.getTenantCode();
if (!tenantCode) {
return null;
}
if (this.token) {
return this.token;
}
this.token = (0, gsb_config_1.getEnvToken)() || null;
if (this.token) {
this.cacheService.setCache('token', this.token, undefined, tenantCode);
return this.token;
}
// If token not loaded yet, try to load from storage
if (!this.token && typeof window !== 'undefined') {
// Try cache
const cachedToken = this.getCacheValue(tenantCode, 'token');
if (cachedToken) {
this.token = cachedToken;
}
}
return this.token;
}
setToken(token, userData, remember = true) {
var _a;
this.token = token;
this.saveToStorage(token, (_a = userData !== null && userData !== void 0 ? userData : this.getCurrentUser()) !== null && _a !== void 0 ? _a : {}, remember);
(0, gsb_config_1.setGsbToken)(token);
}
async sendResetPasswordEmail(email) {
var _a, _b;
const resp = await this.gsbService.runWfFunction({
function: {
name: 'sendResetPasswordEmail'
},
instance: {
prms: {
email
}
}
}, (_a = this.token) !== null && _a !== void 0 ? _a : '', (_b = this.tenantCode) !== null && _b !== void 0 ? _b : '');
if (resp === null || resp === void 0 ? void 0 : resp.response) {
return resp.response;
}
return resp;
}
async changePassword(oldPassword, newPassword, email, resetToken) {
var _a, _b;
return await this.gsbService.changePassword({
oldPassword,
newPassword,
resetToken,
email
}, (_a = this.token) !== null && _a !== void 0 ? _a : '', (_b = this.tenantCode) !== null && _b !== void 0 ? _b : '');
}
/**
* Get the current user data from storage
*/
getCurrentUser() {
if (typeof window === 'undefined') {
return null;
}
if (!this.tenantCode) {
this.tenantCode = (0, gsb_config_1.getGsbTenantCode)();
}
if (!this.tenantCode) {
return null;
}
try {
// Get from cache
return this.getCacheValue(this.tenantCode, 'userData');
}
catch (error) {
console.error('Error getting user data from storage:', error);
return null;
}
}
}
exports.AuthService = AuthService;
//# sourceMappingURL=auth.service.js.map