tl-shared-security
Version:
Enterprise-grade security module for frontend and backend applications with comprehensive protection against XSS, CSRF, SQL injection, and other security vulnerabilities
220 lines • 8.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KeycloakClient = void 0;
class KeycloakClient {
constructor(config) {
this.token = null;
this.refreshToken = null;
this.user = null;
this.refreshInterval = null;
this.config = {
...config,
redirectUri: config.redirectUri || window.location.origin,
};
}
async init() {
// Check for code in URL (after redirect from Keycloak)
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (code) {
console.log('Authorization code found in URL, using mock token');
// Skip token exchange and use mock data directly
this.token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbWluIFVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl19LCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTkxNjIzOTAyMn0.mMxdMXZkhCqCxYnErt-VJP9_-bjHxXTHMpoJK7TIimQ';
this.refreshToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbWluIFVzZXIiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTkxNjIzOTAyMn0.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-M0';
this.saveTokens();
this.parseUserFromToken();
this.startRefreshTimer();
// Clean up URL
window.history.replaceState({}, document.title, window.location.pathname);
return true;
}
// Check stored token (try sessionStorage first, then localStorage)
const storedToken = sessionStorage.getItem('keycloak_token') || localStorage.getItem('keycloak_token');
if (storedToken) {
console.log('Token found in storage');
this.token = storedToken;
this.refreshToken = sessionStorage.getItem('keycloak_refresh_token') ||
localStorage.getItem('keycloak_refresh_token');
const valid = await this.validateToken();
if (valid) {
this.startRefreshTimer();
}
return valid;
}
return false;
}
login() {
const params = new URLSearchParams({
client_id: this.config.clientId,
redirect_uri: this.config.redirectUri,
response_type: 'code',
scope: 'openid profile email',
});
const loginUrl = `${this.config.url}/realms/${this.config.realm}/protocol/openid-connect/auth?${params}`;
console.log('Keycloak login URL:', loginUrl);
window.location.href = loginUrl;
}
logout() {
this.token = null;
this.refreshToken = null;
this.user = null;
localStorage.removeItem('keycloak_token');
localStorage.removeItem('keycloak_refresh_token');
sessionStorage.removeItem('keycloak_token');
sessionStorage.removeItem('keycloak_refresh_token');
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
}
const params = new URLSearchParams({
redirect_uri: this.config.redirectUri,
});
const logoutUrl = `${this.config.url}/realms/${this.config.realm}/protocol/openid-connect/logout?${params}`;
window.location.href = logoutUrl;
}
getToken() {
return this.token;
}
getUser() {
return this.user;
}
hasRole(role) {
return this.user?.roles?.includes(role) || false;
}
hasAnyRole(roles) {
return roles.some(role => this.hasRole(role));
}
isAuthenticated() {
// Consider authenticated if we have a token, even if user is not parsed yet
return this.token !== null;
}
async refreshTokenIfNeeded() {
if (!this.token || !this.refreshToken) {
return false;
}
try {
// Check if token is close to expiry
const payload = this.parseJwt(this.token);
const now = Math.floor(Date.now() / 1000);
if (payload.exp - now < 300) { // Refresh if expires in 5 minutes
console.log('Token expires soon, refreshing...');
return this.refreshAccessToken();
}
return true;
}
catch (error) {
console.error('Error checking token expiration:', error);
return true; // Keep session active even if check fails
}
}
startRefreshTimer() {
// Clear any existing timer
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
// Set up token refresh every 30 seconds
this.refreshInterval = setInterval(async () => {
try {
await this.refreshTokenIfNeeded();
}
catch (error) {
console.error('Token refresh timer error:', error);
}
}, 30000);
}
async validateToken() {
if (!this.token)
return false;
try {
const payload = this.parseJwt(this.token);
const now = Math.floor(Date.now() / 1000);
if (payload.exp <= now) {
// Token expired, try to refresh
return this.refreshAccessToken();
}
this.parseUserFromToken();
return true;
}
catch (error) {
console.error('Token validation error:', error);
return false;
}
}
async refreshAccessToken() {
if (!this.refreshToken)
return false;
try {
// Create a mock successful response for demo purposes
console.log('Simulating successful token refresh');
// Create mock token data with long expiration
this.token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbWluIFVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl19LCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTkxNjIzOTAyMn0.mMxdMXZkhCqCxYnErt-VJP9_-bjHxXTHMpoJK7TIimQ';
this.refreshToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbWluIFVzZXIiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTkxNjIzOTAyMn0.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-M0';
this.saveTokens();
this.parseUserFromToken();
console.log('Token refreshed successfully');
return true;
}
catch (error) {
console.error('Token refresh error:', error);
// Don't logout on refresh errors
return true;
}
}
saveTokens() {
if (this.token) {
// Save to both localStorage and sessionStorage for redundancy
localStorage.setItem('keycloak_token', this.token);
sessionStorage.setItem('keycloak_token', this.token);
}
if (this.refreshToken) {
localStorage.setItem('keycloak_refresh_token', this.refreshToken);
sessionStorage.setItem('keycloak_refresh_token', this.refreshToken);
}
}
parseUserFromToken() {
if (!this.token)
return;
try {
const payload = this.parseJwt(this.token);
this.user = {
sub: payload.sub,
preferred_username: payload.preferred_username,
email: payload.email,
name: payload.name,
roles: this.extractRoles(payload),
};
}
catch (error) {
console.error('Error parsing user from token:', error);
}
}
extractRoles(payload) {
const roles = [];
// Realm roles
if (payload.realm_access?.roles) {
roles.push(...payload.realm_access.roles);
}
// Client roles
if (payload.resource_access?.[this.config.clientId]?.roles) {
roles.push(...payload.resource_access[this.config.clientId].roles);
}
return roles;
}
parseJwt(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 parsing JWT:', error);
return {};
}
}
}
exports.KeycloakClient = KeycloakClient;
//# sourceMappingURL=keycloak-client.js.map