@zestic/oauth-core
Version:
Framework-agnostic OAuth authentication library with support for multiple OAuth flows
160 lines • 5.7 kB
JavaScript
;
/**
* CSRF state validation for OAuth flows
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.StateValidator = void 0;
const OAuthTypes_1 = require("../types/OAuthTypes");
const ErrorHandler_1 = require("../utils/ErrorHandler");
class StateValidator {
constructor(storageAdapter, stateTTL = StateValidator.DEFAULT_STATE_TTL) {
this.storageAdapter = storageAdapter;
this.stateTTL = stateTTL;
}
/**
* Store state with expiry time
*/
async storeState(state) {
try {
const expiryTime = Date.now() + this.stateTTL;
await Promise.all([
this.storageAdapter.setItem(StateValidator.STATE_STORAGE_KEY, state),
this.storageAdapter.setItem(StateValidator.STATE_EXPIRY_KEY, expiryTime.toString()),
]);
}
catch (error) {
throw ErrorHandler_1.ErrorHandler.createError('Failed to store OAuth state', OAuthTypes_1.OAUTH_ERROR_CODES.INVALID_STATE, error instanceof Error ? error : new Error(String(error)));
}
}
/**
* Retrieve stored state
*/
async getStoredState() {
try {
return await this.storageAdapter.getItem(StateValidator.STATE_STORAGE_KEY);
}
catch (error) {
throw ErrorHandler_1.ErrorHandler.createError('Failed to retrieve stored state', OAuthTypes_1.OAUTH_ERROR_CODES.INVALID_STATE, error instanceof Error ? error : new Error(String(error)));
}
}
/**
* Check if stored state is expired
*/
async isStateExpired() {
try {
const expiryTimeStr = await this.storageAdapter.getItem(StateValidator.STATE_EXPIRY_KEY);
if (!expiryTimeStr) {
return true; // No expiry time means expired
}
const expiryTime = parseInt(expiryTimeStr, 10);
return Date.now() >= expiryTime;
}
catch {
return true; // Error retrieving expiry means expired
}
}
/**
* Validate received state against stored state
*/
async validateState(receivedState) {
try {
// Check if state is expired first
if (await this.isStateExpired()) {
await this.clearState(); // Clean up expired state
return false;
}
const storedState = await this.getStoredState();
if (!storedState) {
return false;
}
const isValid = storedState === receivedState;
// Clear state after validation (one-time use)
if (isValid) {
await this.clearState();
}
return isValid;
}
catch (error) {
throw ErrorHandler_1.ErrorHandler.createError('Failed to validate state parameter', OAuthTypes_1.OAUTH_ERROR_CODES.INVALID_STATE, error instanceof Error ? error : new Error(String(error)));
}
}
/**
* Validate state and throw error if invalid
*/
async validateStateOrThrow(receivedState) {
const isValid = await this.validateState(receivedState);
if (!isValid) {
const storedState = await this.getStoredState();
throw ErrorHandler_1.ErrorHandler.handleInvalidState(storedState ?? undefined, receivedState);
}
}
/**
* Clear stored state
*/
async clearState() {
try {
await this.storageAdapter.removeItems([
StateValidator.STATE_STORAGE_KEY,
StateValidator.STATE_EXPIRY_KEY,
]);
}
catch (error) {
throw ErrorHandler_1.ErrorHandler.createError('Failed to clear OAuth state', OAuthTypes_1.OAUTH_ERROR_CODES.INVALID_STATE, error instanceof Error ? error : new Error(String(error)));
}
}
/**
* Check if state exists in storage
*/
async hasStoredState() {
try {
const state = await this.getStoredState();
return state !== null && !(await this.isStateExpired());
}
catch {
return false;
}
}
/**
* Get remaining TTL for stored state
*/
async getStateRemainingTTL() {
try {
const expiryTimeStr = await this.storageAdapter.getItem(StateValidator.STATE_EXPIRY_KEY);
if (!expiryTimeStr) {
return 0;
}
const expiryTime = parseInt(expiryTimeStr, 10);
const remaining = expiryTime - Date.now();
return Math.max(0, remaining);
}
catch {
return 0;
}
}
/**
* Extend state expiry time
*/
async extendStateExpiry(additionalTime) {
try {
const extension = additionalTime ?? this.stateTTL;
const newExpiryTime = Date.now() + extension;
await this.storageAdapter.setItem(StateValidator.STATE_EXPIRY_KEY, newExpiryTime.toString());
}
catch (error) {
throw ErrorHandler_1.ErrorHandler.createError('Failed to extend state expiry', OAuthTypes_1.OAUTH_ERROR_CODES.INVALID_STATE, error instanceof Error ? error : new Error(String(error)));
}
}
/**
* Cleanup expired state (can be called periodically)
*/
async cleanupExpiredState() {
if (await this.isStateExpired()) {
await this.clearState();
}
}
}
exports.StateValidator = StateValidator;
StateValidator.STATE_STORAGE_KEY = 'oauth_state';
StateValidator.STATE_EXPIRY_KEY = 'oauth_state_expiry';
StateValidator.DEFAULT_STATE_TTL = 10 * 60 * 1000; // 10 minutes
//# sourceMappingURL=StateValidator.js.map