saas-platform-auth-client
Version:
Authentication client for SaaS platform auth-service with password reset support
327 lines (323 loc) • 10.1 kB
JavaScript
// src/auth-client.ts
import { HttpClient, AuthManager } from "saas-platform-core-client";
// src/types.ts
import { z } from "zod";
var LoginRequestSchema = z.object({
email: z.string().email("Email inv\xE1lido"),
password: z.string().min(6, "Senha deve ter pelo menos 6 caracteres"),
saasId: z.string().min(1, "SaasId \xE9 obrigat\xF3rio")
});
var RefreshTokenRequestSchema = z.object({
refreshToken: z.string().min(1, "RefreshToken \xE9 obrigat\xF3rio"),
saasId: z.string().min(1, "SaasId \xE9 obrigat\xF3rio")
});
var ValidateTokenRequestSchema = z.object({
token: z.string().min(1, "Token \xE9 obrigat\xF3rio"),
saasId: z.string().min(1, "SaasId \xE9 obrigat\xF3rio")
});
var AdminLoginRequestSchema = z.object({
userId: z.string().uuid("UserId deve ser um GUID v\xE1lido"),
saasId: z.string().min(1, "SaasId \xE9 obrigat\xF3rio")
});
var ForgotPasswordRequestSchema = z.object({
email: z.string().email("Email inv\xE1lido"),
saasId: z.string().min(1, "SaasId \xE9 obrigat\xF3rio")
});
var ResetPasswordRequestSchema = z.object({
email: z.string().email("Email inv\xE1lido"),
resetCode: z.string().length(6, "C\xF3digo deve ter exatamente 6 d\xEDgitos").regex(/^\d{6}$/, "C\xF3digo deve conter apenas n\xFAmeros"),
newPassword: z.string().min(6, "Nova senha deve ter pelo menos 6 caracteres").max(128, "Nova senha deve ter no m\xE1ximo 128 caracteres"),
saasId: z.string().min(1, "SaasId \xE9 obrigat\xF3rio")
});
// src/auth-client.ts
var AuthClient = class {
constructor(config) {
this.callbacks = {};
this.axiosInstance = null;
this.authManager = new AuthManager(config.authServiceUrl, config.saasId);
this.httpClient = new HttpClient(config);
this.httpClient.setBaseURL(config.authServiceUrl);
this.httpClient.setAuthManager(this.authManager);
}
/**
* Login with email and password
* Public endpoint - requires saasId in request body
*/
async login(request) {
const validatedRequest = LoginRequestSchema.parse(request);
const response = await this.httpClient.post("/auth/login", validatedRequest);
if (response.success && response.data) {
let expiresIn = 3600;
if (response.data.expiresAt) {
const expiresAt = new Date(response.data.expiresAt);
const now = /* @__PURE__ */ new Date();
expiresIn = Math.floor((expiresAt.getTime() - now.getTime()) / 1e3);
}
this.authManager.setTokens({
accessToken: response.data.accessToken,
refreshToken: response.data.refreshToken,
expiresIn,
tokenType: "Bearer"
});
}
return response;
}
/**
* Refresh access token using refresh token
* Public endpoint - requires saasId in request body
*/
async refreshToken(request) {
const validatedRequest = RefreshTokenRequestSchema.parse(request);
const response = await this.httpClient.post("/auth/refresh", validatedRequest);
if (response.success && response.data) {
let expiresIn = 3600;
if (response.data.expiresAt) {
const expiresAt = new Date(response.data.expiresAt);
const now = /* @__PURE__ */ new Date();
expiresIn = Math.floor((expiresAt.getTime() - now.getTime()) / 1e3);
}
this.authManager.setTokens({
accessToken: response.data.accessToken,
refreshToken: response.data.refreshToken,
expiresIn,
tokenType: "Bearer"
});
}
return response;
}
/**
* Validate current token
* Protected endpoint - uses Authorization header, saasId extracted from token
*/
async validateToken() {
const token = this.authManager.getAccessToken();
if (!token) {
return {
success: false,
error: {
code: "NO_TOKEN",
message: "No access token available"
},
timestamp: (/* @__PURE__ */ new Date()).toISOString()
};
}
return await this.httpClient.get("/auth/verify");
}
/**
* Admin impersonation login
* Protected endpoint - requires admin token in Authorization header
*/
async adminLogin(request) {
const validatedRequest = AdminLoginRequestSchema.parse(request);
const response = await this.httpClient.post("/auth/impersonate", validatedRequest);
if (response.success && response.data) {
let expiresIn = 3600;
if (response.data.expiresAt) {
const expiresAt = new Date(response.data.expiresAt);
const now = /* @__PURE__ */ new Date();
expiresIn = Math.floor((expiresAt.getTime() - now.getTime()) / 1e3);
}
this.authManager.setTokens({
accessToken: response.data.accessToken,
refreshToken: response.data.refreshToken,
expiresIn,
tokenType: "Bearer"
});
}
return response;
}
/**
* Request password reset
* Public endpoint - requires saasId in request body
*/
async forgotPassword(request) {
const validatedRequest = ForgotPasswordRequestSchema.parse(request);
return await this.httpClient.post("/auth/forgot-password", validatedRequest);
}
/**
* Reset password with verification code
* Public endpoint - requires saasId in request body
*/
async resetPassword(request) {
const validatedRequest = ResetPasswordRequestSchema.parse(request);
return await this.httpClient.post("/auth/reset-password", validatedRequest);
}
/**
* Get service health status
* Public endpoint
*/
async getHealth() {
return await this.httpClient.get("/health");
}
/**
* Logout - clear stored tokens
*/
logout() {
this.authManager.clearTokens();
}
/**
* Check if user is authenticated - considera tanto token válido quanto refresh token
*/
isAuthenticated() {
const result = this.authManager.hasAnyAuthenticationMaterial();
return result;
}
/**
* Get current user info from token - interface mais rica
* Se o token estiver expirado, tenta renovar automaticamente
*/
async getCurrentUser() {
const hasValidToken = this.authManager.hasValidToken();
if (hasValidToken) {
const payload = this.authManager.decodeToken();
if (payload) {
const userInfo = {
id: payload.sub,
email: payload.email,
name: payload.name || payload.email,
role: payload.role || "USER",
saasId: payload.saasId
};
return userInfo;
}
}
const hasAuthMaterial = this.authManager.hasAnyAuthenticationMaterial();
if (hasAuthMaterial) {
try {
const newToken = await this.authManager.ensureValidToken();
if (newToken) {
const payload = this.authManager.decodeToken();
if (payload) {
const userInfo = {
id: payload.sub,
email: payload.email,
name: payload.name || payload.email,
role: payload.role || "USER",
saasId: payload.saasId
};
return userInfo;
}
}
} catch (error) {
return null;
}
}
return null;
}
/**
* Get current access token
*/
getAccessToken() {
return this.authManager.getAccessToken();
}
/**
* Get refresh token
*/
getRefreshToken() {
return this.authManager.getRefreshToken();
}
/**
* Check if token is expired
*/
isTokenExpired() {
return this.authManager.isTokenExpired();
}
/**
* Get SaasId from current token
*/
getSaasId() {
return this.authManager.getSaasIdFromToken();
}
/**
* Get user ID from current token
*/
getUserId() {
return this.authManager.getUserIdFromToken();
}
/**
* Set callbacks para eventos de autenticação
*/
setCallbacks(callbacks) {
this.callbacks = { ...callbacks };
}
/**
* Setup HTTP interceptor automático para Axios
* Intercepta requests, faz refresh transparente, retry automático
*/
setupAxiosInterceptor(axiosInstance) {
this.axiosInstance = axiosInstance;
axiosInstance.interceptors.request.use(
(config) => {
const token = this.getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshToken = this.authManager.getRefreshToken();
if (!refreshToken) {
this.handleSessionExpired();
return Promise.reject(error);
}
try {
const newToken = await this.authManager.ensureValidToken();
if (newToken) {
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return axiosInstance(originalRequest);
} else {
this.handleSessionExpired();
return Promise.reject(error);
}
} catch (refreshError) {
this.handleSessionExpired();
return Promise.reject(error);
}
}
return Promise.reject(error);
}
);
}
/**
* Handle session expired - chama callback apropriado
*/
handleSessionExpired() {
this.clearTokens();
if (this.callbacks.onSessionExpired) {
this.callbacks.onSessionExpired();
} else if (this.callbacks.onLoginRequired) {
this.callbacks.onLoginRequired();
}
}
/**
* Ensure we have a valid access token, refreshing if necessary
*/
async ensureValidToken() {
return await this.authManager.ensureValidToken();
}
/**
* Clear tokens - agora também remove da memória do AuthManager
*/
clearTokens() {
this.authManager.clearTokens();
}
};
// src/index.ts
var VERSION = "1.3.0";
export {
AdminLoginRequestSchema,
AuthClient,
ForgotPasswordRequestSchema,
LoginRequestSchema,
RefreshTokenRequestSchema,
ResetPasswordRequestSchema,
VERSION,
ValidateTokenRequestSchema
};