shogun-core
Version:
SHOGUN SDK - Core library for Shogun SDK
303 lines (302 loc) • 12.8 kB
JavaScript
;
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;