UNPKG

shogun-core

Version:

SHOGUN SDK - Core library for Shogun SDK

303 lines (302 loc) 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OAuthPlugin = void 0; const base_1 = require("../base"); const oauthConnector_1 = require("./oauthConnector"); const logger_1 = require("../../utils/logger"); const errorHandler_1 = require("../../utils/errorHandler"); /** * OAuth Plugin for ShogunCore * Provides authentication with external OAuth providers */ class OAuthPlugin extends base_1.BasePlugin { name = "oauth"; version = "1.0.0"; description = "Provides OAuth authentication with external providers for ShogunCore"; oauthConnector = null; config = {}; /** * @inheritdoc */ initialize(core) { super.initialize(core); // Initialize the OAuth connector this.oauthConnector = new oauthConnector_1.OAuthConnector(this.config); (0, logger_1.log)("OAuth plugin initialized successfully"); } /** * Configure the OAuth plugin with provider settings * @param config - Configuration options for OAuth */ configure(config) { this.config = { ...this.config, ...config }; // If connector is already initialized, update its configuration if (this.oauthConnector) { this.oauthConnector.updateConfig(this.config); (0, logger_1.log)("OAuth connector configuration updated", this.config.providers); } } /** * @inheritdoc */ destroy() { if (this.oauthConnector) { this.oauthConnector.cleanup(); } this.oauthConnector = null; super.destroy(); (0, logger_1.log)("OAuth plugin destroyed"); } /** * Ensure that the OAuth connector is initialized * @private */ assertOAuthConnector() { this.assertInitialized(); if (!this.oauthConnector) { throw new Error("OAuth connector not initialized"); } return this.oauthConnector; } /** * @inheritdoc */ isSupported() { return this.assertOAuthConnector().isSupported(); } /** * @inheritdoc */ getAvailableProviders() { return this.assertOAuthConnector().getAvailableProviders(); } /** * @inheritdoc */ async initiateOAuth(provider) { (0, logger_1.log)(`Initiating OAuth flow with ${provider}`); return this.assertOAuthConnector().initiateOAuth(provider); } /** * @inheritdoc */ async completeOAuth(provider, authCode, state) { (0, logger_1.log)(`Completing OAuth flow with ${provider}`); return this.assertOAuthConnector().completeOAuth(provider, authCode, state); } /** * @inheritdoc */ async generateCredentials(userInfo, provider) { (0, logger_1.log)(`Generating credentials for ${provider} user`); return this.assertOAuthConnector().generateCredentials(userInfo, provider); } /** * Login with OAuth * @param provider - OAuth provider to use * @returns {Promise<AuthResult>} Authentication result * @description Authenticates user using OAuth with external providers */ async login(provider) { (0, logger_1.log)(`OAuth login with ${provider}`); try { const core = this.assertInitialized(); (0, logger_1.log)(`OAuth login attempt with provider: ${provider}`); if (!provider) { throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.VALIDATION, "PROVIDER_REQUIRED", "OAuth provider required for OAuth login"); } if (!this.isSupported()) { throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.ENVIRONMENT, "OAUTH_UNAVAILABLE", "OAuth is not supported in this environment"); } // Check if provider is available const availableProviders = this.getAvailableProviders(); if (!availableProviders.includes(provider)) { throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.VALIDATION, "PROVIDER_NOT_CONFIGURED", `Provider ${provider} is not configured or available`); } // Initiate OAuth flow with the provider const oauthResult = await this.initiateOAuth(provider); if (!oauthResult.success) { throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.AUTHENTICATION, "OAUTH_INITIATION_FAILED", oauthResult.error || "Failed to initiate OAuth flow"); } // In a browser environment, this would redirect to the OAuth provider // The frontend should handle the redirect and then call handleOAuthCallback // with the received code and state when the provider redirects back // Return early with the auth URL that the frontend should use for redirection return { success: true, redirectUrl: oauthResult.authUrl, pendingAuth: true, message: "Redirect to OAuth provider required to complete authentication", provider, authMethod: "oauth", }; } catch (error) { // Handle both ShogunError and generic errors const errorType = error?.type || errorHandler_1.ErrorType.AUTHENTICATION; const errorCode = error?.code || "OAUTH_LOGIN_ERROR"; const errorMessage = error?.message || "Unknown error during OAuth login"; const handledError = errorHandler_1.ErrorHandler.handle(errorType, errorCode, errorMessage, error); return { success: false, error: handledError.message, }; } } /** * Sign up with OAuth * @param provider - OAuth provider to use * @returns {Promise<AuthResult>} Registration result * @description Creates a new user account using OAuth with external providers */ async signUp(provider) { (0, logger_1.log)(`OAuth signup with ${provider}`); try { const core = this.assertInitialized(); (0, logger_1.log)(`OAuth signup attempt with provider: ${provider}`); if (!provider) { throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.VALIDATION, "PROVIDER_REQUIRED", "OAuth provider required for OAuth signup"); } if (!this.isSupported()) { throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.ENVIRONMENT, "OAUTH_UNAVAILABLE", "OAuth is not supported in this environment"); } // Check if provider is available const availableProviders = this.getAvailableProviders(); if (!availableProviders.includes(provider)) { throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.VALIDATION, "PROVIDER_NOT_CONFIGURED", `Provider ${provider} is not configured or available`); } // Initiate OAuth flow with the provider const oauthResult = await this.initiateOAuth(provider); if (!oauthResult.success) { throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.AUTHENTICATION, "OAUTH_INITIATION_FAILED", oauthResult.error || "Failed to initiate OAuth flow"); } // In a browser environment, this would redirect to the OAuth provider // The frontend should handle the redirect and then call handleOAuthCallback // with the received code and state when the provider redirects back // Return early with the auth URL that the frontend should use for redirection return { success: true, redirectUrl: oauthResult.authUrl, pendingAuth: true, message: "Redirect to OAuth provider required to complete registration", provider, authMethod: "oauth", }; } catch (error) { // Handle both ShogunError and generic errors const errorType = error?.type || errorHandler_1.ErrorType.AUTHENTICATION; const errorCode = error?.code || "OAUTH_SIGNUP_ERROR"; const errorMessage = error?.message || "Unknown error during OAuth signup"; const handledError = errorHandler_1.ErrorHandler.handle(errorType, errorCode, errorMessage, error); return { success: false, error: handledError.message, }; } } /** * Handle OAuth callback (for frontend integration) * This method would be called when the OAuth provider redirects back */ async handleOAuthCallback(provider, authCode, state) { try { (0, logger_1.log)(`Handling OAuth callback for ${provider}`); const core = this.assertInitialized(); // Complete the OAuth flow const result = await this.completeOAuth(provider, authCode, state); if (!result.success || !result.userInfo) { throw new Error(result.error || "Failed to complete OAuth flow"); } // Generate credentials from user info const credentials = await this.generateCredentials(result.userInfo, provider); // Set authentication method core.setAuthMethod("oauth"); // Login or sign up the user const authResult = await this._loginOrSignUp(credentials.username, credentials.password); if (authResult.success) { // Store user info in user metadata if (core.user) { await core.user.put({ oauth: { provider, id: result.userInfo.id, email: result.userInfo.email, name: result.userInfo.name, picture: result.userInfo.picture, lastLogin: Date.now(), }, }); } // Emit appropriate event const eventType = authResult.isNewUser ? "auth:signup" : "auth:login"; core.emit(eventType, { userPub: authResult.userPub, username: credentials.username, method: "oauth", provider, }); } return authResult; } catch (error) { (0, logger_1.logError)(`Error handling OAuth callback for ${provider}:`, error); return { success: false, error: error.message || "Failed to handle OAuth callback", }; } } /** * Private helper to login or sign up a user */ async _loginOrSignUp(username, password) { const core = this.assertInitialized(); if (!password) { return { success: false, error: "Password not provided for login/signup.", }; } return new Promise((resolve) => { // Check if user exists first to avoid race conditions core.gun.get(`~@${username}`).once(async (data) => { let authResult; if (data) { // User exists, so log in (0, logger_1.log)(`User ${username} exists, attempting login.`); authResult = await core.login(username, password); authResult.isNewUser = false; } else { // User does not exist, so sign up (0, logger_1.log)(`User ${username} does not exist, attempting signup.`); authResult = await core.signUp(username, password); authResult.isNewUser = true; } resolve(authResult); }); }); } /** * Alias for handleOAuthCallback for backward compatibility * @deprecated Use handleOAuthCallback instead */ async handleSimpleOAuth(provider, authCode, state) { (0, logger_1.log)(`handleSimpleOAuth called (alias for handleOAuthCallback) for ${provider}`); return this.handleOAuthCallback(provider, authCode, state); } /** * Get cached user info for a user */ getCachedUserInfo(userId, provider) { return this.assertOAuthConnector().getCachedUserInfo(userId, provider); } /** * Clear user info cache */ clearUserCache(userId, provider) { this.assertOAuthConnector().clearUserCache(userId, provider); } } exports.OAuthPlugin = OAuthPlugin;