Prettier format pending files
This commit is contained in:
parent
ab154def0e
commit
e141e68ef4
16 changed files with 512 additions and 251 deletions
|
|
@ -11,7 +11,8 @@
|
|||
import { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
import { getAdminClient } from "../_supabase";
|
||||
|
||||
const FOUNDATION_URL = process.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const FOUNDATION_URL =
|
||||
process.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const CLIENT_ID = process.env.FOUNDATION_OAUTH_CLIENT_ID || "aethex_corp";
|
||||
const CLIENT_SECRET = process.env.FOUNDATION_OAUTH_CLIENT_SECRET;
|
||||
const API_BASE = process.env.VITE_API_BASE || "https://aethex.dev";
|
||||
|
|
@ -47,14 +48,18 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) {
|
|||
|
||||
// Handle Foundation errors
|
||||
if (error) {
|
||||
const message = error_description ? decodeURIComponent(String(error_description)) : String(error);
|
||||
const message = error_description
|
||||
? decodeURIComponent(String(error_description))
|
||||
: String(error);
|
||||
return res.redirect(
|
||||
`/login?error=${error}&message=${encodeURIComponent(message)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
return res.redirect(`/login?error=no_code&message=${encodeURIComponent("No authorization code received")}`);
|
||||
return res.redirect(
|
||||
`/login?error=no_code&message=${encodeURIComponent("No authorization code received")}`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -64,7 +69,9 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) {
|
|||
console.warn("[Foundation OAuth] Missing state parameter");
|
||||
}
|
||||
|
||||
console.log("[Foundation OAuth] Received authorization code, initiating token exchange");
|
||||
console.log(
|
||||
"[Foundation OAuth] Received authorization code, initiating token exchange",
|
||||
);
|
||||
|
||||
// Store code in a temporary location for the exchange endpoint
|
||||
// In a real implementation, you'd use a temporary token or session
|
||||
|
|
@ -75,7 +82,9 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) {
|
|||
}
|
||||
|
||||
// Fetch user information from Foundation
|
||||
const userInfo = await fetchUserInfoFromFoundation(exchangeResult.accessToken);
|
||||
const userInfo = await fetchUserInfoFromFoundation(
|
||||
exchangeResult.accessToken,
|
||||
);
|
||||
|
||||
// Sync user to local database
|
||||
await syncUserToLocalDatabase(userInfo);
|
||||
|
|
@ -89,11 +98,12 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) {
|
|||
console.log("[Foundation OAuth] User authenticated:", userInfo.id);
|
||||
|
||||
// Redirect to dashboard (or stored destination)
|
||||
const redirectTo = req.query.redirect_to as string || "/dashboard";
|
||||
const redirectTo = (req.query.redirect_to as string) || "/dashboard";
|
||||
return res.redirect(redirectTo);
|
||||
} catch (error) {
|
||||
console.error("[Foundation OAuth] Callback error:", error);
|
||||
const message = error instanceof Error ? error.message : "Authentication failed";
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Authentication failed";
|
||||
return res.redirect(
|
||||
`/login?error=auth_failed&message=${encodeURIComponent(message)}`,
|
||||
);
|
||||
|
|
@ -105,7 +115,10 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) {
|
|||
* Exchange authorization code for access token
|
||||
* Called from frontend
|
||||
*/
|
||||
export async function handleTokenExchange(req: VercelRequest, res: VercelResponse) {
|
||||
export async function handleTokenExchange(
|
||||
req: VercelRequest,
|
||||
res: VercelResponse,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
|
@ -120,7 +133,9 @@ export async function handleTokenExchange(req: VercelRequest, res: VercelRespons
|
|||
const exchangeResult = await performTokenExchange(code);
|
||||
|
||||
// Fetch user information from Foundation
|
||||
const userInfo = await fetchUserInfoFromFoundation(exchangeResult.accessToken);
|
||||
const userInfo = await fetchUserInfoFromFoundation(
|
||||
exchangeResult.accessToken,
|
||||
);
|
||||
|
||||
// Sync user to local database
|
||||
await syncUserToLocalDatabase(userInfo);
|
||||
|
|
@ -131,7 +146,10 @@ export async function handleTokenExchange(req: VercelRequest, res: VercelRespons
|
|||
`auth_user_id=${userInfo.id}; Path=/; Secure; SameSite=Strict; Max-Age=2592000`,
|
||||
]);
|
||||
|
||||
console.log("[Foundation OAuth] Token exchange successful for user:", userInfo.id);
|
||||
console.log(
|
||||
"[Foundation OAuth] Token exchange successful for user:",
|
||||
userInfo.id,
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
accessToken: exchangeResult.accessToken,
|
||||
|
|
@ -139,7 +157,8 @@ export async function handleTokenExchange(req: VercelRequest, res: VercelRespons
|
|||
});
|
||||
} catch (error) {
|
||||
console.error("[Foundation OAuth] Token exchange error:", error);
|
||||
const message = error instanceof Error ? error.message : "Token exchange failed";
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Token exchange failed";
|
||||
return res.status(400).json({ error: message });
|
||||
}
|
||||
}
|
||||
|
|
@ -147,9 +166,7 @@ export async function handleTokenExchange(req: VercelRequest, res: VercelRespons
|
|||
/**
|
||||
* Exchange authorization code for access token with Foundation
|
||||
*/
|
||||
async function performTokenExchange(
|
||||
code: string,
|
||||
): Promise<{
|
||||
async function performTokenExchange(code: string): Promise<{
|
||||
accessToken: string;
|
||||
tokenType: string;
|
||||
expiresIn: number;
|
||||
|
|
@ -205,7 +222,9 @@ async function performTokenExchange(
|
|||
/**
|
||||
* Fetch user information from Foundation using access token
|
||||
*/
|
||||
async function fetchUserInfoFromFoundation(accessToken: string): Promise<FoundationUserInfo> {
|
||||
async function fetchUserInfoFromFoundation(
|
||||
accessToken: string,
|
||||
): Promise<FoundationUserInfo> {
|
||||
const userInfoEndpoint = `${FOUNDATION_URL}/api/oauth/userinfo`;
|
||||
|
||||
console.log("[Foundation OAuth] Fetching user info from:", userInfoEndpoint);
|
||||
|
|
@ -234,30 +253,36 @@ async function fetchUserInfoFromFoundation(accessToken: string): Promise<Foundat
|
|||
/**
|
||||
* Sync Foundation user to local database
|
||||
*/
|
||||
async function syncUserToLocalDatabase(foundationUser: FoundationUserInfo): Promise<void> {
|
||||
async function syncUserToLocalDatabase(
|
||||
foundationUser: FoundationUserInfo,
|
||||
): Promise<void> {
|
||||
const supabase = getAdminClient();
|
||||
|
||||
console.log("[Foundation OAuth] Syncing user to local database:", foundationUser.id);
|
||||
console.log(
|
||||
"[Foundation OAuth] Syncing user to local database:",
|
||||
foundationUser.id,
|
||||
);
|
||||
|
||||
// Upsert user profile
|
||||
const { error } = await supabase
|
||||
.from("user_profiles")
|
||||
.upsert({
|
||||
id: foundationUser.id,
|
||||
email: foundationUser.email,
|
||||
username: foundationUser.username || null,
|
||||
full_name: foundationUser.full_name || null,
|
||||
avatar_url: foundationUser.avatar_url || null,
|
||||
profile_completed: foundationUser.profile_complete || false,
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
const { error } = await supabase.from("user_profiles").upsert({
|
||||
id: foundationUser.id,
|
||||
email: foundationUser.email,
|
||||
username: foundationUser.username || null,
|
||||
full_name: foundationUser.full_name || null,
|
||||
avatar_url: foundationUser.avatar_url || null,
|
||||
profile_completed: foundationUser.profile_complete || false,
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error("[Foundation OAuth] Failed to sync user profile:", error);
|
||||
throw new Error("Failed to create local user profile");
|
||||
}
|
||||
|
||||
console.log("[Foundation OAuth] User synced successfully:", foundationUser.id);
|
||||
console.log(
|
||||
"[Foundation OAuth] User synced successfully:",
|
||||
foundationUser.id,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
/**
|
||||
* Token Exchange Endpoint
|
||||
*
|
||||
*
|
||||
* Frontend calls this endpoint after receiving the authorization code from Foundation.
|
||||
* It stores the Foundation's access token and returns user information.
|
||||
*/
|
||||
|
||||
import { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
|
||||
const FOUNDATION_URL = process.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const FOUNDATION_URL =
|
||||
process.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const API_BASE = process.env.VITE_API_BASE || "https://aethex.dev";
|
||||
|
||||
export default async function handler(
|
||||
req: VercelRequest,
|
||||
res: VercelResponse,
|
||||
) {
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* Foundation OAuth Callback Handler
|
||||
*
|
||||
*
|
||||
* This endpoint receives the authorization code from aethex.foundation after user authentication.
|
||||
* It exchanges the code for an access token and establishes a session on aethex.dev.
|
||||
*/
|
||||
|
|
@ -8,7 +8,8 @@
|
|||
import { getAdminClient } from "../_supabase";
|
||||
import { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
|
||||
const FOUNDATION_URL = process.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const FOUNDATION_URL =
|
||||
process.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const API_BASE = process.env.VITE_API_BASE || "https://aethex.dev";
|
||||
|
||||
interface FoundationTokenResponse {
|
||||
|
|
@ -23,10 +24,7 @@ interface FoundationTokenResponse {
|
|||
};
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: VercelRequest,
|
||||
res: VercelResponse,
|
||||
) {
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
|
@ -36,11 +34,15 @@ export default async function handler(
|
|||
// Handle Foundation errors
|
||||
if (error) {
|
||||
const errorDesc = req.query.error_description || error;
|
||||
return res.redirect(`/login?error=${error}&message=${encodeURIComponent(String(errorDesc))}`);
|
||||
return res.redirect(
|
||||
`/login?error=${error}&message=${encodeURIComponent(String(errorDesc))}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
return res.redirect("/login?error=no_code&message=Authorization code not received");
|
||||
return res.redirect(
|
||||
"/login?error=no_code&message=Authorization code not received",
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -74,7 +76,7 @@ export default async function handler(
|
|||
const errorData = await tokenResponse.json().catch(() => ({}));
|
||||
console.error("[Foundation OAuth] Token exchange failed:", errorData);
|
||||
return res.redirect(
|
||||
`/login?error=token_exchange&message=${encodeURIComponent("Failed to exchange authorization code")}`
|
||||
`/login?error=token_exchange&message=${encodeURIComponent("Failed to exchange authorization code")}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +84,9 @@ export default async function handler(
|
|||
|
||||
if (!tokenData.access_token || !tokenData.user) {
|
||||
console.error("[Foundation OAuth] Invalid token response");
|
||||
return res.redirect("/login?error=invalid_token&message=Invalid token response");
|
||||
return res.redirect(
|
||||
"/login?error=invalid_token&message=Invalid token response",
|
||||
);
|
||||
}
|
||||
|
||||
// Extract user information from Foundation response
|
||||
|
|
@ -101,25 +105,33 @@ export default async function handler(
|
|||
|
||||
if (fetchError && fetchError.code !== "PGRST116") {
|
||||
// PGRST116 = no rows found (expected for new users)
|
||||
console.error("[Foundation OAuth] Error fetching user profile:", fetchError);
|
||||
console.error(
|
||||
"[Foundation OAuth] Error fetching user profile:",
|
||||
fetchError,
|
||||
);
|
||||
}
|
||||
|
||||
if (!existingProfile) {
|
||||
// Create user profile from Foundation data
|
||||
const { error: createError } = await supabase.from("user_profiles").insert({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
username: user.username || null,
|
||||
full_name: user.full_name || null,
|
||||
profile_completed: user.profile_complete || false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
const { error: createError } = await supabase
|
||||
.from("user_profiles")
|
||||
.insert({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
username: user.username || null,
|
||||
full_name: user.full_name || null,
|
||||
profile_completed: user.profile_complete || false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (createError) {
|
||||
console.error("[Foundation OAuth] Failed to create user profile:", createError);
|
||||
console.error(
|
||||
"[Foundation OAuth] Failed to create user profile:",
|
||||
createError,
|
||||
);
|
||||
return res.redirect(
|
||||
`/login?error=profile_create&message=${encodeURIComponent("Failed to create local user profile")}`
|
||||
`/login?error=profile_create&message=${encodeURIComponent("Failed to create local user profile")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -138,7 +150,7 @@ export default async function handler(
|
|||
} catch (error) {
|
||||
console.error("[Foundation OAuth] Callback error:", error);
|
||||
return res.redirect(
|
||||
`/login?error=unknown&message=${encodeURIComponent("An unexpected error occurred")}`
|
||||
`/login?error=unknown&message=${encodeURIComponent("An unexpected error occurred")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,10 @@ export function useFoundationAuth(): UseFoundationAuthReturn {
|
|||
throw new Error("Invalid response from token exchange");
|
||||
}
|
||||
|
||||
console.log("[Foundation Auth] Token exchange successful for user:", tokenData.user.id);
|
||||
console.log(
|
||||
"[Foundation Auth] Token exchange successful for user:",
|
||||
tokenData.user.id,
|
||||
);
|
||||
|
||||
// Clear auth parameters from URL and storage
|
||||
const url = new URL(window.location.href);
|
||||
|
|
@ -95,16 +98,21 @@ export function useFoundationAuth(): UseFoundationAuthReturn {
|
|||
// Redirect to dashboard or stored destination
|
||||
navigate(redirectTo, { replace: true });
|
||||
} catch (exchangeError) {
|
||||
const message = exchangeError instanceof Error
|
||||
? exchangeError.message
|
||||
: "Failed to exchange authorization code";
|
||||
const message =
|
||||
exchangeError instanceof Error
|
||||
? exchangeError.message
|
||||
: "Failed to exchange authorization code";
|
||||
|
||||
console.error("[Foundation Auth] Token exchange failed:", exchangeError);
|
||||
console.error(
|
||||
"[Foundation Auth] Token exchange failed:",
|
||||
exchangeError,
|
||||
);
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Authentication failed";
|
||||
const message =
|
||||
err instanceof Error ? err.message : "Authentication failed";
|
||||
setError(message);
|
||||
|
||||
console.error("[Foundation Auth] Callback processing error:", err);
|
||||
|
|
@ -150,7 +158,9 @@ export function useFoundationAuthStatus(): {
|
|||
useEffect(() => {
|
||||
// Check for foundation_access_token cookie
|
||||
const cookies = document.cookie.split(";").map((c) => c.trim());
|
||||
const tokenCookie = cookies.find((c) => c.startsWith("foundation_access_token="));
|
||||
const tokenCookie = cookies.find((c) =>
|
||||
c.startsWith("foundation_access_token="),
|
||||
);
|
||||
const userCookie = cookies.find((c) => c.startsWith("auth_user_id="));
|
||||
|
||||
if (tokenCookie && userCookie) {
|
||||
|
|
|
|||
|
|
@ -50,8 +50,10 @@ export function clearFoundationAuth(): void {
|
|||
if (typeof window === "undefined") return;
|
||||
|
||||
// Clear cookies by setting expiration to past
|
||||
document.cookie = "foundation_access_token=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
|
||||
document.cookie = "auth_user_id=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
|
||||
document.cookie =
|
||||
"foundation_access_token=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
|
||||
document.cookie =
|
||||
"auth_user_id=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
|
||||
|
||||
// Clear session storage
|
||||
sessionStorage.removeItem("oauth_code_verifier");
|
||||
|
|
@ -89,7 +91,8 @@ export async function makeAuthenticatedRequest(
|
|||
* Clears local auth state and optionally notifies Foundation
|
||||
*/
|
||||
export async function logoutFromFoundation(): Promise<void> {
|
||||
const FOUNDATION_URL = import.meta.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const FOUNDATION_URL =
|
||||
import.meta.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const token = getFoundationAccessToken();
|
||||
|
||||
// Clear local auth
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@
|
|||
* - GET /api/oauth/userinfo - User info endpoint
|
||||
*/
|
||||
|
||||
const FOUNDATION_URL = import.meta.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const CLIENT_ID = import.meta.env.VITE_FOUNDATION_OAUTH_CLIENT_ID || "aethex_corp";
|
||||
const FOUNDATION_URL =
|
||||
import.meta.env.VITE_FOUNDATION_URL || "https://aethex.foundation";
|
||||
const CLIENT_ID =
|
||||
import.meta.env.VITE_FOUNDATION_OAUTH_CLIENT_ID || "aethex_corp";
|
||||
const API_BASE = import.meta.env.VITE_API_BASE || "https://aethex.dev";
|
||||
|
||||
/**
|
||||
|
|
@ -19,7 +21,8 @@ const API_BASE = import.meta.env.VITE_API_BASE || "https://aethex.dev";
|
|||
* Must be 43-128 characters, URL-safe (A-Z, a-z, 0-9, -, ., _, ~)
|
||||
*/
|
||||
function generateCodeVerifier(): string {
|
||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
||||
const charset =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
||||
const length = 64;
|
||||
let verifier = "";
|
||||
const randomValues = new Uint8Array(length);
|
||||
|
|
@ -104,7 +107,9 @@ export async function getFoundationAuthorizationUrl(options?: {
|
|||
/**
|
||||
* Initiate the Foundation OAuth login flow
|
||||
*/
|
||||
export async function initiateFoundationLogin(redirectTo?: string): Promise<void> {
|
||||
export async function initiateFoundationLogin(
|
||||
redirectTo?: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const authUrl = await getFoundationAuthorizationUrl({ redirectTo });
|
||||
window.location.href = authUrl;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# Axiom Model: Phase 1 Code Migration Scope
|
||||
|
||||
## Moving Identity from aethex.dev (Vercel) to aethex.foundation (Replit)
|
||||
|
||||
**Status:** CRITICAL P0 (Blocks NEXUS & FOUNDATION work)
|
||||
|
|
@ -10,56 +11,62 @@
|
|||
## 1. PAGES TO COPY
|
||||
|
||||
### Authentication & Onboarding Pages
|
||||
| File | Purpose | Notes |
|
||||
|------|---------|-------|
|
||||
| `code/client/pages/Login.tsx` | Login UI + Discord OAuth button | Copy as-is; validate Discord button routing |
|
||||
| `code/client/pages/Signup.tsx` | (if exists) User registration | Copy if present |
|
||||
| `code/client/pages/Onboarding.tsx` | Realm/arm selection, profile setup | Copy all onboarding flow |
|
||||
| `code/client/pages/DiscordVerify.tsx` | Verification code entry for linking | Copy verification flow |
|
||||
|
||||
| File | Purpose | Notes |
|
||||
| ------------------------------------- | ----------------------------------- | ------------------------------------------- |
|
||||
| `code/client/pages/Login.tsx` | Login UI + Discord OAuth button | Copy as-is; validate Discord button routing |
|
||||
| `code/client/pages/Signup.tsx` | (if exists) User registration | Copy if present |
|
||||
| `code/client/pages/Onboarding.tsx` | Realm/arm selection, profile setup | Copy all onboarding flow |
|
||||
| `code/client/pages/DiscordVerify.tsx` | Verification code entry for linking | Copy verification flow |
|
||||
|
||||
### Profile & Settings Pages
|
||||
| File | Purpose | Notes |
|
||||
|------|---------|-------|
|
||||
| `code/client/pages/Profile.tsx` | (or Dashboard) User profile view | Copy public profile viewing |
|
||||
| `code/client/pages/Dashboard.tsx` | User dashboard + OAuthConnections | Copy OAuth linking UI |
|
||||
| `code/client/pages/settings/*` | Profile settings, password reset, etc. | Copy all settings pages |
|
||||
|
||||
| File | Purpose | Notes |
|
||||
| --------------------------------- | -------------------------------------- | --------------------------- |
|
||||
| `code/client/pages/Profile.tsx` | (or Dashboard) User profile view | Copy public profile viewing |
|
||||
| `code/client/pages/Dashboard.tsx` | User dashboard + OAuthConnections | Copy OAuth linking UI |
|
||||
| `code/client/pages/settings/*` | Profile settings, password reset, etc. | Copy all settings pages |
|
||||
|
||||
### Passport Pages
|
||||
| File | Purpose | Notes |
|
||||
|------|---------|-------|
|
||||
| `code/client/pages/SubdomainPassport.tsx` | Creator passport for *.aethex.me | Copy; will fetch from Foundation API |
|
||||
|
||||
| File | Purpose | Notes |
|
||||
| ----------------------------------------- | --------------------------------- | ------------------------------------ |
|
||||
| `code/client/pages/SubdomainPassport.tsx` | Creator passport for \*.aethex.me | Copy; will fetch from Foundation API |
|
||||
|
||||
---
|
||||
|
||||
## 2. CONTEXTS & STATE MANAGEMENT
|
||||
|
||||
| File | Purpose | Dependencies |
|
||||
|------|---------|--------------|
|
||||
| `code/client/contexts/AuthContext.tsx` | Central auth state, loginProvider, linkProvider | Depends on Supabase client |
|
||||
| `code/client/contexts/DiscordActivityContext.tsx` | Discord Activity SDK state | Optional; copy if Activity is needed |
|
||||
| `code/client/contexts/ThemeContext.tsx` | Theme switching | Dependency; copy |
|
||||
| File | Purpose | Dependencies |
|
||||
| ------------------------------------------------- | ----------------------------------------------- | ------------------------------------ |
|
||||
| `code/client/contexts/AuthContext.tsx` | Central auth state, loginProvider, linkProvider | Depends on Supabase client |
|
||||
| `code/client/contexts/DiscordActivityContext.tsx` | Discord Activity SDK state | Optional; copy if Activity is needed |
|
||||
| `code/client/contexts/ThemeContext.tsx` | Theme switching | Dependency; copy |
|
||||
|
||||
---
|
||||
|
||||
## 3. COMPONENTS TO COPY
|
||||
|
||||
### Auth & OAuth Components
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `code/client/components/settings/OAuthConnections.tsx` | OAuth provider cards (Discord, etc.) |
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------------------------------------------- | ------------------------------------------------- |
|
||||
| `code/client/components/settings/OAuthConnections.tsx` | OAuth provider cards (Discord, etc.) |
|
||||
| `code/client/components/admin/AdminDiscordManagement.tsx` | Admin UI for role mappings (optional for Phase 1) |
|
||||
|
||||
### Profile & Passport Components
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------------------------------------- | ------------------------ |
|
||||
| `code/client/components/passport/PassportSummary.tsx` | Renders creator passport |
|
||||
| `code/client/components/ErrorBoundary.tsx` | Error handling |
|
||||
| `code/client/components/LoadingScreen.tsx` | Loading UI |
|
||||
| `code/client/components/Layout.tsx` | App layout & header |
|
||||
| `code/client/components/ErrorBoundary.tsx` | Error handling |
|
||||
| `code/client/components/LoadingScreen.tsx` | Loading UI |
|
||||
| `code/client/components/Layout.tsx` | App layout & header |
|
||||
|
||||
### Shared UI Components
|
||||
| Directory | Purpose |
|
||||
|-----------|---------|
|
||||
|
||||
| Directory | Purpose |
|
||||
| ----------------------------- | --------------------------------------- |
|
||||
| `code/client/components/ui/*` | All Radix UI & design system components |
|
||||
|
||||
---
|
||||
|
|
@ -67,36 +74,40 @@
|
|||
## 4. API ENDPOINTS & SERVERLESS FUNCTIONS TO COPY
|
||||
|
||||
### Discord OAuth Endpoints
|
||||
| File | Endpoint | Purpose |
|
||||
|------|----------|---------|
|
||||
| `code/api/discord/oauth/start.ts` | `GET /api/discord/oauth/start` | Redirect to Discord authorization |
|
||||
|
||||
| File | Endpoint | Purpose |
|
||||
| ------------------------------------ | --------------------------------- | ---------------------------------- |
|
||||
| `code/api/discord/oauth/start.ts` | `GET /api/discord/oauth/start` | Redirect to Discord authorization |
|
||||
| `code/api/discord/oauth/callback.ts` | `GET /api/discord/oauth/callback` | Handle Discord callback, link user |
|
||||
| `code/api/discord/verify-code.ts` | `POST /api/discord/verify-code` | Verify 6-digit code for linking |
|
||||
| `code/api/discord/link.ts` | `POST /api/discord/link` | Link Discord account by code |
|
||||
| `code/api/discord/sync-roles.ts` | `POST /api/discord/sync-roles` | Assign Discord roles after linking |
|
||||
| `code/api/discord/verify-code.ts` | `POST /api/discord/verify-code` | Verify 6-digit code for linking |
|
||||
| `code/api/discord/link.ts` | `POST /api/discord/link` | Link Discord account by code |
|
||||
| `code/api/discord/sync-roles.ts` | `POST /api/discord/sync-roles` | Assign Discord roles after linking |
|
||||
|
||||
### Profile & Auth Endpoints
|
||||
| File | Endpoint | Purpose |
|
||||
|------|----------|---------|
|
||||
| `code/api/profile/ensure.ts` | `POST /api/profile/ensure` | Create or ensure user profile exists |
|
||||
| `code/api/user/*` | Various | User data endpoints (review for auth deps) |
|
||||
|
||||
| File | Endpoint | Purpose |
|
||||
| ---------------------------- | -------------------------- | ------------------------------------------ |
|
||||
| `code/api/profile/ensure.ts` | `POST /api/profile/ensure` | Create or ensure user profile exists |
|
||||
| `code/api/user/*` | Various | User data endpoints (review for auth deps) |
|
||||
|
||||
### Passport Endpoints
|
||||
| File | Endpoint | Purpose |
|
||||
|------|----------|---------|
|
||||
|
||||
| File | Endpoint | Purpose |
|
||||
| ------------------------------------------- | --------------------------------------- | ------------------------- |
|
||||
| `code/api/passport/subdomain/[username].ts` | `GET /api/passport/subdomain/:username` | Creator passport JSON API |
|
||||
| `code/api/passport/project/[slug].ts` | `GET /api/passport/project/:slug` | Project passport JSON API |
|
||||
| `code/api/passport/project/[slug].ts` | `GET /api/passport/project/:slug` | Project passport JSON API |
|
||||
|
||||
---
|
||||
|
||||
## 5. DATABASE MIGRATIONS TO COPY
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| File | Purpose |
|
||||
| --------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| `code/supabase/migrations/20250107_add_discord_integration.sql` | Discord tables (discord_links, discord_verifications, discord_role_mappings) |
|
||||
| All other user/auth-related migrations | Copy all identity-related schema |
|
||||
| All other user/auth-related migrations | Copy all identity-related schema |
|
||||
|
||||
**Supabase Tables Required:**
|
||||
|
||||
- `user_profiles`
|
||||
- `user_auth_identities`
|
||||
- `discord_links`
|
||||
|
|
@ -108,6 +119,7 @@
|
|||
## 6. LIBRARIES & DEPENDENCIES
|
||||
|
||||
### Required npm packages (verify in aethex.dev package.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"@supabase/supabase-js": "^2.x",
|
||||
|
|
@ -124,6 +136,7 @@
|
|||
```
|
||||
|
||||
### Environment Variables Needed
|
||||
|
||||
```
|
||||
VITE_SUPABASE_URL=https://kmdeisowhtsalsekkzqd.supabase.co
|
||||
VITE_SUPABASE_ANON_KEY=sb_publishable_...
|
||||
|
|
@ -138,14 +151,15 @@ VITE_API_BASE=https://aethex.foundation (after switchover)
|
|||
|
||||
## 7. CRITICAL ADAPTATIONS FOR REPLIT TARGET
|
||||
|
||||
| Current (aethex.dev) | Needed for aethex.foundation |
|
||||
|----------------------|------------------------------|
|
||||
| Current (aethex.dev) | Needed for aethex.foundation |
|
||||
| ------------------------------------------ | ------------------------------------------- |
|
||||
| Vercel serverless functions (`code/api/*`) | Express or Remix server endpoints on Replit |
|
||||
| `VITE_API_BASE=https://aethex.dev` | `VITE_API_BASE=https://aethex.foundation` |
|
||||
| Vite + React on Vercel | Vite + React on Replit (same) |
|
||||
| Uses Vercel environment variables | Use Replit Secrets or .env |
|
||||
| `VITE_API_BASE=https://aethex.dev` | `VITE_API_BASE=https://aethex.foundation` |
|
||||
| Vite + React on Vercel | Vite + React on Replit (same) |
|
||||
| Uses Vercel environment variables | Use Replit Secrets or .env |
|
||||
|
||||
### Key Refactoring Points
|
||||
|
||||
1. **API Endpoints:** Vercel's `/api/*` files may need conversion to Express routes in `code/server/index.ts` or equivalent Replit server.
|
||||
2. **Base URLs:** Update all `VITE_API_BASE` references to point to `aethex.foundation` instead of `aethex.dev`.
|
||||
3. **OAuth Redirect URIs:** Update Discord OAuth app to use `aethex.foundation` callback URL.
|
||||
|
|
@ -158,6 +172,7 @@ VITE_API_BASE=https://aethex.foundation (after switchover)
|
|||
After copying existing code, build 3 new OAuth 2.0 endpoints on aethex.foundation:
|
||||
|
||||
### 1. `/authorize` (Foundation SSO Authorization)
|
||||
|
||||
**Purpose:** Initiate login flow for external apps (aethex.dev)
|
||||
|
||||
```
|
||||
|
|
@ -167,6 +182,7 @@ GET /authorize?client_id=AETHEX_DEV&redirect_uri=https://aethex.dev/auth/callbac
|
|||
**Response:** Redirect user to `/login` with state preserved
|
||||
|
||||
### 2. `/token` (Foundation SSO Token Exchange)
|
||||
|
||||
**Purpose:** Exchange auth code for JWT token
|
||||
|
||||
```
|
||||
|
|
@ -188,6 +204,7 @@ Returns:
|
|||
```
|
||||
|
||||
### 3. `/userinfo` (Foundation SSO User Info)
|
||||
|
||||
**Purpose:** Fetch current logged-in user info (used by aethex.dev after login)
|
||||
|
||||
```
|
||||
|
|
@ -211,6 +228,7 @@ Returns:
|
|||
## 9. MIGRATION CHECKLIST
|
||||
|
||||
### Before Starting Phase 1
|
||||
|
||||
- [ ] Verify all auth code is in `code/client/pages/` and `code/api/discord/*`
|
||||
- [ ] List all custom hooks used in auth flow (use-toast, etc.)
|
||||
- [ ] Document all Supabase queries used for auth
|
||||
|
|
@ -218,10 +236,11 @@ Returns:
|
|||
- [ ] Create a "mirror" directory structure on aethex.foundation (Replit)
|
||||
|
||||
### During Phase 1
|
||||
|
||||
- [ ] Copy all page files (Login, Signup, Onboarding, Dashboard, etc.)
|
||||
- [ ] Copy all context files (AuthContext, DiscordActivityContext, ThemeContext)
|
||||
- [ ] Copy all component files (OAuthConnections, PassportSummary, etc.)
|
||||
- [ ] Copy all API endpoint files (discord/oauth/*, profile/ensure.ts, passport/*)
|
||||
- [ ] Copy all API endpoint files (discord/oauth/_, profile/ensure.ts, passport/_)
|
||||
- [ ] Copy all Supabase migrations
|
||||
- [ ] Copy tailwind.config.js and global.css for styling
|
||||
- [ ] Adapt all import paths for new directory structure
|
||||
|
|
@ -230,6 +249,7 @@ Returns:
|
|||
- [ ] Set up environment variables on Replit
|
||||
|
||||
### Testing Phase 1
|
||||
|
||||
- [ ] Can users log in via Discord on aethex.foundation?
|
||||
- [ ] Can users view their profile?
|
||||
- [ ] Can users link additional OAuth providers?
|
||||
|
|
@ -253,15 +273,15 @@ Returns:
|
|||
|
||||
## 11. ESTIMATED EFFORT
|
||||
|
||||
| Task | Estimate |
|
||||
|------|----------|
|
||||
| Audit & document auth code | 2-3 hours |
|
||||
| Copy & adapt page files | 4-6 hours |
|
||||
| Copy & adapt API endpoints | 3-4 hours |
|
||||
| Fix imports & dependencies | 2-3 hours |
|
||||
| Test login flow | 2-3 hours |
|
||||
| Build SSO endpoints | 4-6 hours |
|
||||
| **Total Phase 1** | **17-25 hours** |
|
||||
| Task | Estimate |
|
||||
| -------------------------- | --------------- |
|
||||
| Audit & document auth code | 2-3 hours |
|
||||
| Copy & adapt page files | 4-6 hours |
|
||||
| Copy & adapt API endpoints | 3-4 hours |
|
||||
| Fix imports & dependencies | 2-3 hours |
|
||||
| Test login flow | 2-3 hours |
|
||||
| Build SSO endpoints | 4-6 hours |
|
||||
| **Total Phase 1** | **17-25 hours** |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ FOUNDATION_OAUTH_CLIENT_SECRET=bcoEtyQVGr6Z4557658eUXpDF5FDni2TGNahH3HT-FtylNrLC
|
|||
```
|
||||
|
||||
**Important:**
|
||||
|
||||
- ✅ Keep `FOUNDATION_OAUTH_CLIENT_SECRET` **secure** (never commit to git)
|
||||
- ✅ Use deployment platform's secret management (Vercel > Settings > Environment Variables)
|
||||
- ✅ Mark secret variables as "Encrypted"
|
||||
|
|
@ -30,11 +31,12 @@ Before deploying, confirm:
|
|||
|
||||
- [ ] aethex.foundation is running and accessible
|
||||
- [ ] `/api/oauth/authorize` endpoint responding
|
||||
- [ ] `/api/oauth/token` endpoint responding
|
||||
- [ ] `/api/oauth/token` endpoint responding
|
||||
- [ ] `/api/oauth/userinfo` endpoint responding
|
||||
- [ ] OAuth credentials valid (client_id, client_secret)
|
||||
|
||||
**Quick Test:**
|
||||
|
||||
```bash
|
||||
# Test token endpoint
|
||||
curl -X POST https://aethex.foundation/api/oauth/token \
|
||||
|
|
@ -62,6 +64,7 @@ Ask Foundation admin to verify these are registered.
|
|||
### Step 1: Set Environment Variables
|
||||
|
||||
**Vercel:**
|
||||
|
||||
1. Go to Project Settings > Environment Variables
|
||||
2. Add three variables:
|
||||
- `VITE_FOUNDATION_URL` = `https://aethex.foundation`
|
||||
|
|
@ -71,6 +74,7 @@ Ask Foundation admin to verify these are registered.
|
|||
4. Save
|
||||
|
||||
**Railway/Other:**
|
||||
|
||||
- Add to `.env` file in deployment
|
||||
- Or configure in platform's settings
|
||||
- Restart deployment for changes to take effect
|
||||
|
|
@ -98,6 +102,7 @@ code/
|
|||
```
|
||||
|
||||
**Deploy command:**
|
||||
|
||||
```bash
|
||||
# For Vercel
|
||||
vercel deploy --prod
|
||||
|
|
@ -114,22 +119,26 @@ docker push <registry>/aethex-dev
|
|||
### Step 3: Verify Deployment
|
||||
|
||||
1. **Check environment variables:**
|
||||
|
||||
```bash
|
||||
# On deployed app, check logs for env var loading
|
||||
# Should see Foundation URL in console (not secret though!)
|
||||
```
|
||||
|
||||
2. **Visit login page:**
|
||||
|
||||
- Go to https://aethex.dev/login
|
||||
- Should see "Login with Foundation" button
|
||||
- No console errors
|
||||
|
||||
3. **Test OAuth flow:**
|
||||
|
||||
- Click "Login with Foundation"
|
||||
- Should redirect to https://aethex.foundation/api/oauth/authorize
|
||||
- Page should show Foundation login (or auth screen)
|
||||
|
||||
4. **Check callback endpoint:**
|
||||
|
||||
- Network tab should show POST to `/auth/callback/exchange`
|
||||
- Should return 200 with access_token
|
||||
|
||||
|
|
@ -162,6 +171,7 @@ ECONNREFUSED ⚠️ Foundation unreachabl
|
|||
### Test 1: Happy Path (Successful Login)
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Visit https://aethex.dev/login
|
||||
2. Click "Login with Foundation"
|
||||
3. Enter test credentials on Foundation
|
||||
|
|
@ -172,6 +182,7 @@ ECONNREFUSED ⚠️ Foundation unreachabl
|
|||
**Expected Result:** ✅ Logged in, cookies set, profile synced
|
||||
|
||||
**Check:**
|
||||
|
||||
```bash
|
||||
# In browser console:
|
||||
document.cookie # Should show foundation_access_token
|
||||
|
|
@ -184,6 +195,7 @@ SELECT * FROM user_profiles WHERE email = '<test-user-email>';
|
|||
### Test 2: Error: Invalid Code
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Manually modify callback URL: `?code=invalid_code_123`
|
||||
2. Press Enter
|
||||
|
||||
|
|
@ -192,6 +204,7 @@ SELECT * FROM user_profiles WHERE email = '<test-user-email>';
|
|||
### Test 3: Network Error
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Stop/pause Foundation service
|
||||
2. Attempt login
|
||||
3. Foundation redirects back with code
|
||||
|
|
@ -202,6 +215,7 @@ SELECT * FROM user_profiles WHERE email = '<test-user-email>';
|
|||
### Test 4: Logout and Re-login
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Logout from dashboard (if logout button exists)
|
||||
2. Check cookies are cleared
|
||||
3. Login again with Foundation
|
||||
|
|
@ -212,6 +226,7 @@ SELECT * FROM user_profiles WHERE email = '<test-user-email>';
|
|||
### Test 5: Multiple Browsers
|
||||
|
||||
Test on:
|
||||
|
||||
- [ ] Chrome/Chromium
|
||||
- [ ] Firefox
|
||||
- [ ] Safari
|
||||
|
|
@ -255,6 +270,7 @@ After 1-2 weeks of successful deployment:
|
|||
|
||||
1. Remove old Discord OAuth code (optional)
|
||||
2. Delete deprecated files:
|
||||
|
||||
- `code/api/discord/oauth/start.ts`
|
||||
- `code/api/discord/oauth/callback.ts`
|
||||
- `code/api/discord/link.ts`
|
||||
|
|
@ -273,15 +289,17 @@ If critical issues occur:
|
|||
### Immediate Rollback (< 1 hour)
|
||||
|
||||
1. **Revert deployment:**
|
||||
|
||||
```bash
|
||||
# Vercel
|
||||
vercel rollback
|
||||
|
||||
|
||||
# Railway
|
||||
railway rollback <previous-deployment>
|
||||
```
|
||||
|
||||
2. **Remove environment variables:**
|
||||
|
||||
- Remove VITE_FOUNDATION_URL
|
||||
- Remove FOUNDATION_OAUTH_CLIENT_ID
|
||||
- Remove FOUNDATION_OAUTH_CLIENT_SECRET
|
||||
|
|
@ -293,6 +311,7 @@ If critical issues occur:
|
|||
### If Rollback Fails
|
||||
|
||||
Contact Foundation admin for assistance with:
|
||||
|
||||
- OAuth endpoint status
|
||||
- User session validation
|
||||
- Database consistency
|
||||
|
|
@ -304,16 +323,19 @@ Contact Foundation admin for assistance with:
|
|||
### Key Metrics to Monitor
|
||||
|
||||
1. **Auth Success Rate**
|
||||
|
||||
- Target: >99%
|
||||
- Alert threshold: <95%
|
||||
- What to check: Logs for "Token exchange" errors
|
||||
|
||||
2. **Token Exchange Time**
|
||||
|
||||
- Target: <500ms
|
||||
- Alert threshold: >2000ms
|
||||
- What to check: Network latency to Foundation
|
||||
|
||||
3. **Foundation Connectivity**
|
||||
|
||||
- Monitor: Foundation endpoint availability
|
||||
- Alert on: Connection failures to /api/oauth/token
|
||||
- Fallback: Maintenance page if Foundation down
|
||||
|
|
@ -330,11 +352,11 @@ Contact Foundation admin for assistance with:
|
|||
// In application logs/dashboard:
|
||||
const metrics = {
|
||||
total_login_attempts: 1000,
|
||||
successful_logins: 990, // 99%
|
||||
successful_logins: 990, // 99%
|
||||
failed_token_exchange: 5,
|
||||
failed_user_sync: 2,
|
||||
failed_state_validation: 3,
|
||||
avg_token_exchange_time_ms: 234
|
||||
avg_token_exchange_time_ms: 234,
|
||||
};
|
||||
```
|
||||
|
||||
|
|
@ -344,13 +366,13 @@ const metrics = {
|
|||
|
||||
### Common Issues During Deployment
|
||||
|
||||
| Issue | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| "Client secret not found" | Missing env var | Add FOUNDATION_OAUTH_CLIENT_SECRET |
|
||||
| "Redirect URI mismatch" | URI not registered on Foundation | Ask Foundation admin to register |
|
||||
| "Token exchange failed 401" | Invalid credentials | Verify client_id and client_secret |
|
||||
| "User sync failed" | Supabase error | Check user_profiles table schema |
|
||||
| "Cookies not set" | SameSite policy blocking | Check cookie headers on response |
|
||||
| Issue | Cause | Solution |
|
||||
| --------------------------- | -------------------------------- | ---------------------------------- |
|
||||
| "Client secret not found" | Missing env var | Add FOUNDATION_OAUTH_CLIENT_SECRET |
|
||||
| "Redirect URI mismatch" | URI not registered on Foundation | Ask Foundation admin to register |
|
||||
| "Token exchange failed 401" | Invalid credentials | Verify client_id and client_secret |
|
||||
| "User sync failed" | Supabase error | Check user_profiles table schema |
|
||||
| "Cookies not set" | SameSite policy blocking | Check cookie headers on response |
|
||||
|
||||
### Debug Commands
|
||||
|
||||
|
|
@ -371,12 +393,14 @@ psql -c "SELECT COUNT(*) FROM user_profiles;"
|
|||
### Getting Help
|
||||
|
||||
1. **Check logs:**
|
||||
|
||||
- Deployment platform logs (Vercel Dashboard, Railway Dashboard)
|
||||
- Application logs (if available)
|
||||
- Browser console (F12)
|
||||
- Network tab (check requests/responses)
|
||||
|
||||
2. **Verify configuration:**
|
||||
|
||||
- Environment variables set correctly
|
||||
- Foundation endpoints accessible
|
||||
- Redirect URI registered
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ GET https://aethex.foundation/api/oauth/authorize
|
|||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `client_id` - aethex_corp (identifies this app)
|
||||
- `redirect_uri` - Where Foundation redirects after auth
|
||||
- `response_type` - Always "code" (OAuth 2.0 authorization code flow)
|
||||
|
|
@ -98,6 +99,7 @@ grant_type=authorization_code
|
|||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJ...",
|
||||
|
|
@ -143,22 +145,26 @@ See `code/api/auth/callback.ts` → `fetchUserInfoFromFoundation()`
|
|||
### How PKCE Works
|
||||
|
||||
1. **Client generates code verifier:**
|
||||
|
||||
```javascript
|
||||
verifier = randomString(64 chars, URL-safe)
|
||||
// Example: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
|
||||
```
|
||||
|
||||
2. **Client generates code challenge:**
|
||||
|
||||
```javascript
|
||||
challenge = base64url(SHA256(verifier))
|
||||
challenge = base64url(SHA256(verifier));
|
||||
```
|
||||
|
||||
3. **Client sends challenge with authorization request:**
|
||||
|
||||
```
|
||||
GET /api/oauth/authorize?...&code_challenge=...&code_challenge_method=S256
|
||||
```
|
||||
|
||||
4. **Server stores challenge, validates with verifier on token exchange:**
|
||||
|
||||
```
|
||||
POST /api/oauth/token?...&code_verifier=...
|
||||
```
|
||||
|
|
@ -169,6 +175,7 @@ See `code/api/auth/callback.ts` → `fetchUserInfoFromFoundation()`
|
|||
```
|
||||
|
||||
**Why PKCE?**
|
||||
|
||||
- Prevents authorization code interception attacks
|
||||
- Secure for mobile apps and single-page applications
|
||||
- Required by OAuth 2.1 best practices
|
||||
|
|
@ -193,6 +200,7 @@ Updated to show Foundation OAuth button:
|
|||
```
|
||||
|
||||
Features:
|
||||
|
||||
- Initiates Foundation OAuth flow
|
||||
- Generates PKCE parameters
|
||||
- Stores verifier and state in sessionStorage
|
||||
|
|
@ -204,16 +212,16 @@ Core OAuth functionality:
|
|||
|
||||
```typescript
|
||||
// Generate PKCE parameters
|
||||
async function generatePKCEParams(): Promise<{ verifier, challenge }>
|
||||
async function generatePKCEParams(): Promise<{ verifier; challenge }>;
|
||||
|
||||
// Build authorization URL
|
||||
async function getFoundationAuthorizationUrl(options?): Promise<string>
|
||||
async function getFoundationAuthorizationUrl(options?): Promise<string>;
|
||||
|
||||
// Initiate login
|
||||
async function initiateFoundationLogin(redirectTo?: string): Promise<void>
|
||||
async function initiateFoundationLogin(redirectTo?: string): Promise<void>;
|
||||
|
||||
// Exchange code for token (called from backend)
|
||||
async function exchangeCodeForToken(code: string): Promise<TokenResponse>
|
||||
async function exchangeCodeForToken(code: string): Promise<TokenResponse>;
|
||||
```
|
||||
|
||||
#### 3. useFoundationAuth Hook (`code/client/hooks/use-foundation-auth.ts`)
|
||||
|
|
@ -235,6 +243,7 @@ useFoundationAuthStatus(): { isAuthenticated, userId }
|
|||
**Route:** `GET /auth/callback?code=...&state=...`
|
||||
|
||||
**Flow:**
|
||||
|
||||
1. Receive authorization code from Foundation
|
||||
2. Validate state token (CSRF protection)
|
||||
3. Exchange code for access token
|
||||
|
|
@ -244,29 +253,30 @@ useFoundationAuthStatus(): { isAuthenticated, userId }
|
|||
7. Redirect to dashboard
|
||||
|
||||
**Code:**
|
||||
|
||||
```typescript
|
||||
async function handleCallback(req, res) {
|
||||
// 1. Get code from URL
|
||||
const { code, state } = req.query;
|
||||
|
||||
|
||||
// 2. Validate state
|
||||
validateState(state);
|
||||
|
||||
|
||||
// 3. Exchange for token
|
||||
const token = await performTokenExchange(code);
|
||||
|
||||
|
||||
// 4. Fetch user info
|
||||
const user = await fetchUserInfoFromFoundation(token);
|
||||
|
||||
|
||||
// 5. Sync to database
|
||||
await syncUserToLocalDatabase(user);
|
||||
|
||||
|
||||
// 6. Set cookies
|
||||
res.setHeader("Set-Cookie", [
|
||||
`foundation_access_token=${token}; ...`,
|
||||
`auth_user_id=${user.id}; ...`
|
||||
`auth_user_id=${user.id}; ...`,
|
||||
]);
|
||||
|
||||
|
||||
// 7. Redirect
|
||||
return res.redirect("/dashboard");
|
||||
}
|
||||
|
|
@ -279,7 +289,7 @@ async function handleCallback(req, res) {
|
|||
```typescript
|
||||
async function handleTokenExchange(req, res) {
|
||||
const { code } = req.body;
|
||||
|
||||
|
||||
// Exchange code with Foundation
|
||||
// Fetch user info
|
||||
// Sync to database
|
||||
|
|
@ -314,16 +324,16 @@ For authenticated API requests:
|
|||
```typescript
|
||||
// Get token from cookie
|
||||
const token = document.cookie
|
||||
.split(';')
|
||||
.find(c => c.trim().startsWith('foundation_access_token='))
|
||||
?.split('=')[1];
|
||||
.split(";")
|
||||
.find((c) => c.trim().startsWith("foundation_access_token="))
|
||||
?.split("=")[1];
|
||||
|
||||
// Use in requests
|
||||
fetch('/api/user/profile', {
|
||||
fetch("/api/user/profile", {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
credentials: 'include'
|
||||
credentials: "include",
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -333,13 +343,14 @@ On logout:
|
|||
|
||||
```typescript
|
||||
// Clear cookies
|
||||
document.cookie = 'foundation_access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC;';
|
||||
document.cookie = 'auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;';
|
||||
document.cookie =
|
||||
"foundation_access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
|
||||
document.cookie = "auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
|
||||
|
||||
// Optional: Notify Foundation of logout
|
||||
await fetch(`${FOUNDATION_URL}/api/oauth/logout`, {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -376,13 +387,13 @@ Corp Local Database (user_profiles table):
|
|||
|
||||
```typescript
|
||||
await supabase.from("user_profiles").upsert({
|
||||
id: foundationUser.id, // Match by ID
|
||||
id: foundationUser.id, // Match by ID
|
||||
email: foundationUser.email,
|
||||
username: foundationUser.username,
|
||||
full_name: foundationUser.full_name,
|
||||
avatar_url: foundationUser.avatar_url,
|
||||
profile_completed: foundationUser.profile_complete,
|
||||
updated_at: new Date().toISOString()
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -395,6 +406,7 @@ await supabase.from("user_profiles").upsert({
|
|||
### Local Testing
|
||||
|
||||
1. **Set up environment:**
|
||||
|
||||
```bash
|
||||
export VITE_FOUNDATION_URL=http://localhost:3001 # Or staging URL
|
||||
export FOUNDATION_OAUTH_CLIENT_ID=aethex_corp
|
||||
|
|
@ -402,6 +414,7 @@ await supabase.from("user_profiles").upsert({
|
|||
```
|
||||
|
||||
2. **Test flow:**
|
||||
|
||||
- Visit `http://localhost:5173/login`
|
||||
- Click "Login with Foundation"
|
||||
- Should redirect to Foundation auth page
|
||||
|
|
@ -417,6 +430,7 @@ await supabase.from("user_profiles").upsert({
|
|||
### Error Scenarios
|
||||
|
||||
**Invalid code:**
|
||||
|
||||
```
|
||||
GET /auth/callback?code=invalid
|
||||
→ 400 "Token exchange failed"
|
||||
|
|
@ -424,6 +438,7 @@ GET /auth/callback?code=invalid
|
|||
```
|
||||
|
||||
**Invalid state:**
|
||||
|
||||
```
|
||||
GET /auth/callback?code=...&state=wrong_state
|
||||
→ Error "Invalid state token - possible CSRF attack"
|
||||
|
|
@ -431,6 +446,7 @@ GET /auth/callback?code=...&state=wrong_state
|
|||
```
|
||||
|
||||
**Foundation down:**
|
||||
|
||||
```
|
||||
POST /api/oauth/token → ECONNREFUSED
|
||||
→ Error "Failed to exchange code"
|
||||
|
|
@ -442,6 +458,7 @@ POST /api/oauth/token → ECONNREFUSED
|
|||
## Files Modified/Created
|
||||
|
||||
### New Files
|
||||
|
||||
```
|
||||
code/
|
||||
├── client/
|
||||
|
|
@ -454,6 +471,7 @@ code/
|
|||
```
|
||||
|
||||
### Modified Files
|
||||
|
||||
```
|
||||
code/
|
||||
└── client/
|
||||
|
|
@ -461,7 +479,9 @@ code/
|
|||
```
|
||||
|
||||
### Deprecated Files
|
||||
|
||||
These can be removed after testing completes:
|
||||
|
||||
```
|
||||
code/api/discord/oauth/start.ts
|
||||
code/api/discord/oauth/callback.ts
|
||||
|
|
@ -492,14 +512,17 @@ code/api/discord/verify-code.ts
|
|||
### Key Metrics
|
||||
|
||||
1. **Auth Success Rate**
|
||||
|
||||
- Target: >99%
|
||||
- Alert if: <95%
|
||||
|
||||
2. **Token Exchange Time**
|
||||
|
||||
- Target: <500ms
|
||||
- Alert if: >2s
|
||||
|
||||
3. **Error Categories**
|
||||
|
||||
- Track: invalid_code, state_mismatch, timeout, etc.
|
||||
|
||||
4. **Foundation Connectivity**
|
||||
|
|
@ -528,6 +551,7 @@ Key points to log:
|
|||
**Cause:** Foundation didn't redirect back properly
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Check `redirect_uri` matches exactly (case-sensitive)
|
||||
2. Verify Foundation OAuth settings
|
||||
3. Check browser console for JavaScript errors
|
||||
|
|
@ -537,6 +561,7 @@ Key points to log:
|
|||
**Cause:** CSRF validation failed
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Check that state is generated consistently
|
||||
2. Verify sessionStorage isn't cleared between redirect
|
||||
3. Check for multiple browser tabs (different state per tab)
|
||||
|
|
@ -546,6 +571,7 @@ Key points to log:
|
|||
**Cause:** Foundation token endpoint unavailable or code invalid
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Check Foundation is running and `/api/oauth/token` accessible
|
||||
2. Verify `client_id` and `client_secret` are correct
|
||||
3. Check code hasn't expired (usually 10 minutes)
|
||||
|
|
@ -556,6 +582,7 @@ Key points to log:
|
|||
**Cause:** Database error during sync
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Check `user_profiles` table exists and has proper schema
|
||||
2. Verify Supabase connection and permissions
|
||||
3. Check user_id isn't duplicated in database
|
||||
|
|
|
|||
|
|
@ -157,7 +157,11 @@
|
|||
"source": "code/supabase/migrations/20250107_add_discord_integration.sql",
|
||||
"type": "migration",
|
||||
"priority": "high",
|
||||
"tables": ["discord_links", "discord_verifications", "discord_role_mappings"]
|
||||
"tables": [
|
||||
"discord_links",
|
||||
"discord_verifications",
|
||||
"discord_role_mappings"
|
||||
]
|
||||
}
|
||||
],
|
||||
"config": [
|
||||
|
|
|
|||
|
|
@ -1,22 +1,29 @@
|
|||
# Axiom Model - Phase 1 Migration
|
||||
|
||||
## How to Use This Package
|
||||
|
||||
You have 3 files to help with the Phase 1 code migration:
|
||||
|
||||
### 1. **PHASE1-CHECKLIST.txt** ← START HERE
|
||||
|
||||
Simple, easy-to-read checklist of all files to copy.
|
||||
|
||||
- ✅ Just check off items as you copy them
|
||||
- 📋 Best for quick reference while working
|
||||
- 👥 Share this with your team
|
||||
|
||||
### 2. **PHASE1-FILES.json**
|
||||
|
||||
Machine-readable list of all files with metadata.
|
||||
|
||||
- 🤖 Use this if you want to script/automate the copy
|
||||
- 📊 Includes priority levels, categories, and notes
|
||||
- 💻 Parse this in your favorite tool
|
||||
|
||||
### 3. **AXIOM-MODEL-PHASE1-SCOPE.md**
|
||||
|
||||
Complete detailed documentation.
|
||||
|
||||
- 📖 Full explanation of every file and why it matters
|
||||
- 🎯 Includes adaptation notes and potential blockers
|
||||
- 🔍 Reference this if you get stuck on something
|
||||
|
|
@ -43,12 +50,15 @@ Complete detailed documentation.
|
|||
---
|
||||
|
||||
## Estimated Time
|
||||
|
||||
**17-25 hours** for complete Phase 1 migration
|
||||
|
||||
---
|
||||
|
||||
## Questions?
|
||||
|
||||
Refer to AXIOM-MODEL-PHASE1-SCOPE.md sections:
|
||||
|
||||
- **Import issues?** → Section 7: Libraries & Dependencies
|
||||
- **What file goes where?** → Sections 1-5: Complete file listing
|
||||
- **How do I adapt?** → Section 7: Critical Adaptations
|
||||
|
|
@ -57,11 +67,14 @@ Refer to AXIOM-MODEL-PHASE1-SCOPE.md sections:
|
|||
---
|
||||
|
||||
## Next: Phase 2
|
||||
|
||||
Once Phase 1 is complete, Phase 2 involves:
|
||||
|
||||
- Supabase permission migration (Foundation gets full access)
|
||||
- `aethex.dev` loses direct write access to user_profiles
|
||||
|
||||
Then Phase 3:
|
||||
|
||||
- Reroute `aethex.dev` login → `aethex.foundation` (SSO)
|
||||
|
||||
**Done!** The Axiom Model is live.
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ Foundation Endpoints:
|
|||
### New Implementation Files
|
||||
|
||||
#### Frontend OAuth Client (`code/client/lib/foundation-oauth.ts`)
|
||||
|
||||
✅ **Implements PKCE (Proof Key for Code Exchange)**
|
||||
|
||||
- Generates code verifier (64-char random, URL-safe)
|
||||
|
|
@ -68,14 +69,16 @@ Foundation Endpoints:
|
|||
- Stores verifier/state in sessionStorage
|
||||
|
||||
**Key Functions:**
|
||||
|
||||
```typescript
|
||||
getFoundationAuthorizationUrl() // Build auth URL
|
||||
initiateFoundationLogin() // Redirect to Foundation
|
||||
exchangeCodeForToken() // Exchange code (called from backend)
|
||||
validateState() // CSRF validation
|
||||
getFoundationAuthorizationUrl(); // Build auth URL
|
||||
initiateFoundationLogin(); // Redirect to Foundation
|
||||
exchangeCodeForToken(); // Exchange code (called from backend)
|
||||
validateState(); // CSRF validation
|
||||
```
|
||||
|
||||
#### Token & Cookie Management (`code/client/lib/foundation-auth.ts`)
|
||||
|
||||
✅ **Handles session cookies and authentication state**
|
||||
|
||||
- Get/check Foundation access token from cookies
|
||||
|
|
@ -85,16 +88,18 @@ validateState() // CSRF validation
|
|||
- Logout notification to Foundation
|
||||
|
||||
**Key Functions:**
|
||||
|
||||
```typescript
|
||||
getFoundationAccessToken() // Get JWT from cookie
|
||||
getAuthUserId() // Get user UUID from cookie
|
||||
isFoundationAuthenticated() // Check auth status
|
||||
clearFoundationAuth() // Logout
|
||||
makeAuthenticatedRequest() // API call with token
|
||||
logoutFromFoundation() // Full logout flow
|
||||
getFoundationAccessToken(); // Get JWT from cookie
|
||||
getAuthUserId(); // Get user UUID from cookie
|
||||
isFoundationAuthenticated(); // Check auth status
|
||||
clearFoundationAuth(); // Logout
|
||||
makeAuthenticatedRequest(); // API call with token
|
||||
logoutFromFoundation(); // Full logout flow
|
||||
```
|
||||
|
||||
#### OAuth Callback Hook (`code/client/hooks/use-foundation-auth.ts`)
|
||||
|
||||
✅ **Detects OAuth callback and handles token exchange**
|
||||
|
||||
- Detects authorization code in URL
|
||||
|
|
@ -105,16 +110,20 @@ logoutFromFoundation() // Full logout flow
|
|||
- Error handling with user feedback
|
||||
|
||||
**Key Functions:**
|
||||
|
||||
```typescript
|
||||
useFoundationAuth() // Process OAuth callback
|
||||
useFoundationAuthStatus() // Check auth status
|
||||
useFoundationAuth(); // Process OAuth callback
|
||||
useFoundationAuthStatus(); // Check auth status
|
||||
```
|
||||
|
||||
#### OAuth Callback Handler (`code/api/auth/callback.ts`)
|
||||
|
||||
✅ **Backend endpoint for OAuth flow completion**
|
||||
|
||||
**Two routes:**
|
||||
|
||||
1. `GET /auth/callback?code=...&state=...`
|
||||
|
||||
- Receives authorization code from Foundation
|
||||
- Validates state (CSRF)
|
||||
- Exchanges code for token
|
||||
|
|
@ -130,15 +139,17 @@ useFoundationAuthStatus() // Check auth status
|
|||
- Sets secure cookies
|
||||
|
||||
**Key Functions:**
|
||||
|
||||
```typescript
|
||||
handleCallback() // GET /auth/callback
|
||||
handleTokenExchange() // POST /auth/callback/exchange
|
||||
performTokenExchange() // Code → token exchange
|
||||
fetchUserInfoFromFoundation() // Fetch user profile
|
||||
syncUserToLocalDatabase() // Upsert to local DB
|
||||
handleCallback(); // GET /auth/callback
|
||||
handleTokenExchange(); // POST /auth/callback/exchange
|
||||
performTokenExchange(); // Code → token exchange
|
||||
fetchUserInfoFromFoundation(); // Fetch user profile
|
||||
syncUserToLocalDatabase(); // Upsert to local DB
|
||||
```
|
||||
|
||||
#### Updated Login Page (`code/client/pages/Login.tsx`)
|
||||
|
||||
✅ **New Foundation OAuth button**
|
||||
|
||||
- Added "Login with Foundation" button (primary option)
|
||||
|
|
@ -147,6 +158,7 @@ syncUserToLocalDatabase() // Upsert to local DB
|
|||
- Discord now managed by Foundation instead
|
||||
|
||||
**Changes:**
|
||||
|
||||
```typescript
|
||||
// NEW
|
||||
<Button onClick={() => initiateFoundationLogin()}>
|
||||
|
|
@ -160,6 +172,7 @@ syncUserToLocalDatabase() // Upsert to local DB
|
|||
### Configuration Files
|
||||
|
||||
#### Example Environment Variables (`.env.foundation-oauth.example`)
|
||||
|
||||
```bash
|
||||
VITE_FOUNDATION_URL=https://aethex.foundation
|
||||
FOUNDATION_OAUTH_CLIENT_ID=aethex_corp
|
||||
|
|
@ -171,6 +184,7 @@ FOUNDATION_OAUTH_CLIENT_SECRET=bcoEtyQVGr6Z4557658eUXpDF5FDni2TGNahH3HT-FtylNrLC
|
|||
✅ **Complete Documentation Provided:**
|
||||
|
||||
1. **FOUNDATION-OAUTH-IMPLEMENTATION.md** (601 lines)
|
||||
|
||||
- Complete technical guide
|
||||
- PKCE explanation
|
||||
- All endpoints documented
|
||||
|
|
@ -268,7 +282,7 @@ FOUNDATION_OAUTH_CLIENT_SECRET=bcoEtyQVGr6Z4557658eUXpDF5FDni2TGNahH3HT-FtylNrLC
|
|||
302 /dashboard
|
||||
↓
|
||||
17. User appears logged in on aethex.dev dashboard ✅
|
||||
|
||||
|
||||
Session established:
|
||||
- foundation_access_token in cookie (HttpOnly, Secure)
|
||||
- auth_user_id in cookie (Secure)
|
||||
|
|
@ -297,7 +311,7 @@ Server stores challenge, issues code
|
|||
On token exchange:
|
||||
Client sends: code_verifier
|
||||
Server verifies: SHA256(verifier) == stored_challenge
|
||||
|
||||
|
||||
Result: Code can't be reused even if intercepted
|
||||
```
|
||||
|
||||
|
|
@ -335,20 +349,20 @@ document.cookie
|
|||
const token = getFoundationAccessToken();
|
||||
|
||||
// Make authenticated request
|
||||
fetch('/api/user/profile', {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
credentials: 'include' // Include cookies
|
||||
fetch("/api/user/profile", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
credentials: "include", // Include cookies
|
||||
});
|
||||
|
||||
// Or use helper
|
||||
import { makeAuthenticatedRequest } from '@/lib/foundation-auth';
|
||||
const response = await makeAuthenticatedRequest('/api/user/profile');
|
||||
import { makeAuthenticatedRequest } from "@/lib/foundation-auth";
|
||||
const response = await makeAuthenticatedRequest("/api/user/profile");
|
||||
```
|
||||
|
||||
### Logout
|
||||
|
||||
```typescript
|
||||
import { logoutFromFoundation } from '@/lib/foundation-auth';
|
||||
import { logoutFromFoundation } from "@/lib/foundation-auth";
|
||||
|
||||
// Logout button click handler
|
||||
await logoutFromFoundation();
|
||||
|
|
@ -398,13 +412,13 @@ Corp Local Database:
|
|||
```typescript
|
||||
// On OAuth callback, sync user:
|
||||
await supabase.from("user_profiles").upsert({
|
||||
id: foundationUser.id, // Use Foundation UUID
|
||||
id: foundationUser.id, // Use Foundation UUID
|
||||
email: foundationUser.email,
|
||||
username: foundationUser.username,
|
||||
full_name: foundationUser.full_name,
|
||||
avatar_url: foundationUser.avatar_url,
|
||||
profile_completed: foundationUser.profile_complete,
|
||||
updated_at: new Date().toISOString()
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Result:
|
||||
|
|
@ -494,15 +508,15 @@ Why? Foundation now handles all Discord OAuth
|
|||
|
||||
## Key Differences from Before
|
||||
|
||||
| Aspect | Before Phase 3 | After Phase 3 |
|
||||
|--------|---|---|
|
||||
| **Identity Provider** | aethex.dev (local) | aethex.foundation (remote) |
|
||||
| **Discord OAuth** | Handled on aethex.dev | Handled on Foundation |
|
||||
| **Session Token** | Supabase JWT | Foundation JWT |
|
||||
| **User Profile Owner** | aethex.dev | aethex.foundation |
|
||||
| **Login Flow** | Local Discord button | Redirect to Foundation |
|
||||
| **Profile Updates** | Direct to Supabase | Sync from Foundation |
|
||||
| **Passport Issuer** | Distributed | aethex.foundation (Single source of truth) |
|
||||
| Aspect | Before Phase 3 | After Phase 3 |
|
||||
| ---------------------- | --------------------- | ------------------------------------------ |
|
||||
| **Identity Provider** | aethex.dev (local) | aethex.foundation (remote) |
|
||||
| **Discord OAuth** | Handled on aethex.dev | Handled on Foundation |
|
||||
| **Session Token** | Supabase JWT | Foundation JWT |
|
||||
| **User Profile Owner** | aethex.dev | aethex.foundation |
|
||||
| **Login Flow** | Local Discord button | Redirect to Foundation |
|
||||
| **Profile Updates** | Direct to Supabase | Sync from Foundation |
|
||||
| **Passport Issuer** | Distributed | aethex.foundation (Single source of truth) |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -529,7 +543,9 @@ Phase 3 is **successfully deployed when:**
|
|||
## Documentation Provided
|
||||
|
||||
### Implementation Guide
|
||||
|
||||
📖 **`FOUNDATION-OAUTH-IMPLEMENTATION.md`** (601 lines)
|
||||
|
||||
- Technical deep-dive
|
||||
- PKCE explanation
|
||||
- All endpoints documented
|
||||
|
|
@ -538,7 +554,9 @@ Phase 3 is **successfully deployed when:**
|
|||
- Troubleshooting guide
|
||||
|
||||
### Deployment Guide
|
||||
|
||||
📖 **`DEPLOYMENT-CHECKLIST.md`** (470 lines)
|
||||
|
||||
- Step-by-step deployment
|
||||
- Environment setup
|
||||
- Testing plan
|
||||
|
|
@ -547,7 +565,9 @@ Phase 3 is **successfully deployed when:**
|
|||
- Success criteria
|
||||
|
||||
### Code Documentation
|
||||
|
||||
✅ **Inline code comments**
|
||||
|
||||
- `foundation-oauth.ts` - PKCE + auth flow
|
||||
- `foundation-auth.ts` - Token management
|
||||
- `use-foundation-auth.ts` - React hooks
|
||||
|
|
@ -558,12 +578,14 @@ Phase 3 is **successfully deployed when:**
|
|||
## Next Steps
|
||||
|
||||
### Immediate (Today)
|
||||
|
||||
1. Review implementation
|
||||
2. Verify credentials are correct
|
||||
3. Set environment variables in deployment platform
|
||||
4. Deploy to staging
|
||||
|
||||
### Short-term (This Week)
|
||||
|
||||
1. Test complete OAuth flow
|
||||
2. Verify user syncing
|
||||
3. Monitor logs for errors
|
||||
|
|
@ -571,6 +593,7 @@ Phase 3 is **successfully deployed when:**
|
|||
5. Deploy to production
|
||||
|
||||
### Long-term (Next Week+)
|
||||
|
||||
1. Monitor metrics (auth success rate, response times)
|
||||
2. Remove old Discord OAuth code
|
||||
3. Update user documentation
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ This Phase 3 implementation transforms `aethex.dev` from an identity provider in
|
|||
### Client-side OAuth Utilities
|
||||
|
||||
1. **`code/client/lib/foundation-oauth.ts`**
|
||||
|
||||
- OAuth flow initialization
|
||||
- URL generation for Foundation redirect
|
||||
- `initiateFoundationLogin(redirectTo?)` - Main entry point for login
|
||||
|
|
@ -19,6 +20,7 @@ This Phase 3 implementation transforms `aethex.dev` from an identity provider in
|
|||
- Storage helpers for redirect destinations
|
||||
|
||||
2. **`code/client/lib/foundation-auth.ts`**
|
||||
|
||||
- Token and session management
|
||||
- Cookie handling (`foundation_access_token`, `auth_user_id`)
|
||||
- User profile fetching from Foundation
|
||||
|
|
@ -37,6 +39,7 @@ This Phase 3 implementation transforms `aethex.dev` from an identity provider in
|
|||
### Backend OAuth Endpoints
|
||||
|
||||
4. **`code/api/auth/foundation-callback.ts`**
|
||||
|
||||
- Callback endpoint: `GET /api/auth/foundation-callback?code=...&state=...`
|
||||
- Validates authorization code from Foundation
|
||||
- Exchanges code for access token
|
||||
|
|
@ -54,6 +57,7 @@ This Phase 3 implementation transforms `aethex.dev` from an identity provider in
|
|||
### Configuration & Documentation
|
||||
|
||||
6. **`code/.env.foundation-oauth.example`**
|
||||
|
||||
- Example environment variables
|
||||
- `VITE_FOUNDATION_URL` - Foundation identity provider URL
|
||||
- `FOUNDATION_OAUTH_CLIENT_SECRET` - OAuth credentials
|
||||
|
|
@ -73,6 +77,7 @@ This Phase 3 implementation transforms `aethex.dev` from an identity provider in
|
|||
### **`code/client/pages/Login.tsx`**
|
||||
|
||||
**Changes:**
|
||||
|
||||
- Added Foundation URL import
|
||||
- Added `initiateFoundationLogin` import
|
||||
- Replaced Discord OAuth button with "Login with Foundation" button
|
||||
|
|
@ -82,14 +87,16 @@ This Phase 3 implementation transforms `aethex.dev` from an identity provider in
|
|||
- **Other Options** (Roblox, Ethereum)
|
||||
|
||||
**Old button removed:**
|
||||
|
||||
```javascript
|
||||
// REMOVED - Discord OAuth now handled by Foundation
|
||||
<Button onClick={() => window.location.href = "/api/discord/oauth/start"}>
|
||||
<Button onClick={() => (window.location.href = "/api/discord/oauth/start")}>
|
||||
Discord
|
||||
</Button>
|
||||
```
|
||||
|
||||
**New button added:**
|
||||
|
||||
```javascript
|
||||
<Button onClick={() => initiateFoundationLogin(redirectTo)}>
|
||||
<Shield /> Login with Foundation
|
||||
|
|
@ -145,6 +152,7 @@ FOUNDATION_OAUTH_CLIENT_SECRET=<secret-from-foundation-setup>
|
|||
```
|
||||
|
||||
**Obtain these from Foundation admin:**
|
||||
|
||||
1. After Foundation's Phase 1 setup is complete
|
||||
2. Request OAuth client secret for `aethex-corp`
|
||||
3. Verify Foundation endpoints are operational
|
||||
|
|
@ -154,26 +162,28 @@ FOUNDATION_OAUTH_CLIENT_SECRET=<secret-from-foundation-setup>
|
|||
|
||||
## Key Differences from Phase 2
|
||||
|
||||
| Aspect | Phase 2 | Phase 3 |
|
||||
|--------|--------|--------|
|
||||
| **Auth Provider** | Supabase (local) | Foundation (remote) |
|
||||
| **Identity Issuer** | aethex.dev | aethex.foundation |
|
||||
| **Discord OAuth** | Handled locally | Handled by Foundation |
|
||||
| **User Source** | Local Supabase | Foundation → synced locally |
|
||||
| **Session Tokens** | Supabase JWT | Foundation JWT |
|
||||
| **Profile Updates** | Direct to Supabase | Via Foundation sync |
|
||||
| **Logout** | Clear local session | Notify Foundation + clear local |
|
||||
| Aspect | Phase 2 | Phase 3 |
|
||||
| ------------------- | ------------------- | ------------------------------- |
|
||||
| **Auth Provider** | Supabase (local) | Foundation (remote) |
|
||||
| **Identity Issuer** | aethex.dev | aethex.foundation |
|
||||
| **Discord OAuth** | Handled locally | Handled by Foundation |
|
||||
| **User Source** | Local Supabase | Foundation → synced locally |
|
||||
| **Session Tokens** | Supabase JWT | Foundation JWT |
|
||||
| **Profile Updates** | Direct to Supabase | Via Foundation sync |
|
||||
| **Logout** | Clear local session | Notify Foundation + clear local |
|
||||
|
||||
---
|
||||
|
||||
## What Happens to Discord OAuth?
|
||||
|
||||
**Old flow (Phase 2):**
|
||||
|
||||
- User clicks Discord button on aethex.dev
|
||||
- aethex.dev handles OAuth with Discord
|
||||
- Discord connects to aethex.dev's Supabase
|
||||
|
||||
**New flow (Phase 3):**
|
||||
|
||||
- User clicks "Login with Foundation" on aethex.dev
|
||||
- Redirected to aethex.foundation for authentication
|
||||
- User connects Discord on Foundation (if needed)
|
||||
|
|
@ -196,15 +206,15 @@ import { useFoundationAuth } from '@/hooks/use-foundation-auth';
|
|||
export function App() {
|
||||
// Detects auth code in URL and handles exchange
|
||||
const { isProcessing, error } = useFoundationAuth();
|
||||
|
||||
|
||||
if (isProcessing) {
|
||||
return <LoadingScreen />; // Show while processing Foundation callback
|
||||
}
|
||||
|
||||
|
||||
if (error) {
|
||||
// Handle auth error
|
||||
}
|
||||
|
||||
|
||||
return <AppContent />;
|
||||
}
|
||||
```
|
||||
|
|
@ -218,11 +228,11 @@ import { useFoundationAuthStatus } from '@/hooks/use-foundation-auth';
|
|||
|
||||
function ProtectedRoute() {
|
||||
const { isAuthenticated } = useFoundationAuthStatus();
|
||||
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to="/login" />;
|
||||
}
|
||||
|
||||
|
||||
return <Dashboard />;
|
||||
}
|
||||
```
|
||||
|
|
@ -232,15 +242,15 @@ function ProtectedRoute() {
|
|||
Send Foundation token with requests:
|
||||
|
||||
```typescript
|
||||
import { getFoundationAccessToken } from '@/lib/foundation-auth';
|
||||
import { getFoundationAccessToken } from "@/lib/foundation-auth";
|
||||
|
||||
const token = getFoundationAccessToken();
|
||||
|
||||
fetch('/api/user/profile', {
|
||||
fetch("/api/user/profile", {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
credentials: 'include'
|
||||
credentials: "include",
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -266,6 +276,7 @@ fetch('/api/user/profile', {
|
|||
## Next Steps
|
||||
|
||||
### Immediate (Week 1)
|
||||
|
||||
1. ✅ Implement Phase 3 code (this document)
|
||||
2. ⏳ Set Foundation OAuth credentials
|
||||
3. ⏳ Deploy to staging
|
||||
|
|
@ -273,6 +284,7 @@ fetch('/api/user/profile', {
|
|||
5. ⏳ Monitor for errors and issues
|
||||
|
||||
### Short-term (Week 2-3)
|
||||
|
||||
1. ⏳ Verify all existing users can re-authenticate
|
||||
2. ⏳ Confirm user profile syncing works
|
||||
3. ⏳ Test role/permission inheritance from Foundation
|
||||
|
|
@ -280,6 +292,7 @@ fetch('/api/user/profile', {
|
|||
5. ⏳ Update documentation
|
||||
|
||||
### Future (Phase 4+)
|
||||
|
||||
1. ⏳ Remove email/password auth from aethex.dev
|
||||
2. ⏳ Remove Roblox/Ethereum OAuth (centralize at Foundation)
|
||||
3. ⏳ Implement cross-domain SSO
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ The Phase 3 implementation is **complete and ready for deployment**. aethex.dev
|
|||
## What You Need To Know
|
||||
|
||||
### Before Foundation Migrate
|
||||
|
||||
- **aethex.dev** handled all authentication (Discord OAuth, email/password)
|
||||
- User identity was distributed across multiple systems
|
||||
- Each application had its own auth logic
|
||||
|
||||
### After Phase 3 Deployed
|
||||
|
||||
- **aethex.foundation** is the authoritative identity provider
|
||||
- **aethex.dev** redirects users to Foundation for authentication
|
||||
- All Discord connections handled by Foundation
|
||||
|
|
@ -42,12 +44,14 @@ FOUNDATION_OAUTH_CLIENT_SECRET=<secret-provided-by-foundation>
|
|||
The following files are new and handle Foundation OAuth:
|
||||
|
||||
**Client-side:**
|
||||
|
||||
- `code/client/lib/foundation-oauth.ts` - OAuth flow
|
||||
- `code/client/lib/foundation-auth.ts` - Token management
|
||||
- `code/client/hooks/use-foundation-auth.ts` - React hooks
|
||||
- `code/client/pages/Login.tsx` - UPDATED with Foundation button
|
||||
|
||||
**Server-side:**
|
||||
|
||||
- `code/api/auth/foundation-callback.ts` - OAuth callback handler
|
||||
- `code/api/auth/exchange-token.ts` - Token exchange endpoint
|
||||
|
||||
|
|
@ -64,18 +68,22 @@ The following files are new and handle Foundation OAuth:
|
|||
## Key Changes in This Phase
|
||||
|
||||
### Login Page
|
||||
|
||||
- **Old:** Discord button redirected to local `/api/discord/oauth/start`
|
||||
- **New:** "Login with Foundation" button redirects to `aethex.foundation`
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
- **Old:** Local Supabase auth → Discord OAuth locally → Session on aethex.dev
|
||||
- **New:** Redirect to Foundation → User auth on Foundation → Session on aethex.dev with Foundation token
|
||||
|
||||
### User Profile
|
||||
|
||||
- **Old:** Stored directly in aethex.dev's Supabase
|
||||
- **New:** Synced from Foundation's Supabase to aethex.dev's local copy
|
||||
|
||||
### Discord Management
|
||||
|
||||
- **Old:** aethex.dev handled all Discord connections
|
||||
- **New:** Foundation handles all Discord connections; aethex.dev consumes the result
|
||||
|
||||
|
|
@ -84,6 +92,7 @@ The following files are new and handle Foundation OAuth:
|
|||
## Important Files
|
||||
|
||||
### New Components (Phase 3 Specific)
|
||||
|
||||
```
|
||||
code/
|
||||
├── client/
|
||||
|
|
@ -106,6 +115,7 @@ code/
|
|||
```
|
||||
|
||||
### Configuration Files
|
||||
|
||||
```
|
||||
code/
|
||||
└── .env.foundation-oauth.example ← Example env vars
|
||||
|
|
@ -154,6 +164,7 @@ Discord OAuth is now **managed entirely by aethex.foundation**.
|
|||
## User Experience After Phase 3
|
||||
|
||||
### For New Users
|
||||
|
||||
1. Visit aethex.dev/login
|
||||
2. See "Login with Foundation" button (primary option)
|
||||
3. Click it
|
||||
|
|
@ -162,6 +173,7 @@ Discord OAuth is now **managed entirely by aethex.foundation**.
|
|||
6. Complete onboarding with pre-filled Foundation data
|
||||
|
||||
### For Existing Users
|
||||
|
||||
1. Existing sessions will be cleared (they had aethex.dev Supabase tokens)
|
||||
2. They'll be redirected to login page
|
||||
3. They click "Login with Foundation"
|
||||
|
|
@ -208,15 +220,18 @@ Discord OAuth is now **managed entirely by aethex.foundation**.
|
|||
If critical issues arise:
|
||||
|
||||
1. **Revert code:**
|
||||
|
||||
```bash
|
||||
git revert <Phase3-commit-hash>
|
||||
```
|
||||
|
||||
2. **Restore environment:**
|
||||
|
||||
- Remove VITE_FOUNDATION_URL
|
||||
- Remove FOUNDATION_OAUTH_CLIENT_SECRET
|
||||
|
||||
3. **Tell users:**
|
||||
|
||||
- "We've temporarily disabled Foundation integration"
|
||||
- "Please use local login or Discord OAuth"
|
||||
|
||||
|
|
@ -229,6 +244,7 @@ If critical issues arise:
|
|||
## Deployment Recommendations
|
||||
|
||||
### Staging Deployment (First)
|
||||
|
||||
1. Deploy Phase 3 code to staging
|
||||
2. Set Foundation OAuth credentials on staging
|
||||
3. Test according to `PHASE3-TESTING-PLAN.md`
|
||||
|
|
@ -236,6 +252,7 @@ If critical issues arise:
|
|||
5. Monitor staging for 24 hours
|
||||
|
||||
### Production Deployment
|
||||
|
||||
1. Create backup of current auth system
|
||||
2. Deploy Phase 3 code gradually (canary deployment if possible)
|
||||
3. Set Foundation OAuth credentials in production
|
||||
|
|
@ -244,6 +261,7 @@ If critical issues arise:
|
|||
6. Communicate with users
|
||||
|
||||
### Monitoring
|
||||
|
||||
- Auth success rate (target >99%)
|
||||
- Token exchange time (target <2s)
|
||||
- Error messages in logs
|
||||
|
|
@ -280,12 +298,14 @@ A: Users link Discord on Foundation instead. No linking needed on aethex.dev.
|
|||
## Next Steps
|
||||
|
||||
### Week 1: Setup
|
||||
|
||||
1. ✅ Code implemented (DONE)
|
||||
2. ⏳ Get Foundation OAuth credentials
|
||||
3. ⏳ Set environment variables
|
||||
4. ⏳ Deploy to staging
|
||||
|
||||
### Week 2: Testing
|
||||
|
||||
5. ⏳ Test complete auth flow
|
||||
6. ⏳ Test error scenarios
|
||||
7. ⏳ Test on multiple browsers
|
||||
|
|
@ -293,12 +313,14 @@ A: Users link Discord on Foundation instead. No linking needed on aethex.dev.
|
|||
9. ⏳ Get team approval
|
||||
|
||||
### Week 3: Deployment
|
||||
|
||||
10. ⏳ Deploy to production
|
||||
11. ⏳ Monitor closely for issues
|
||||
12. ⏳ Document any bugs found
|
||||
13. ⏳ Communicate with users
|
||||
|
||||
### Week 4+: Optimization
|
||||
|
||||
14. ⏳ Remove old Discord OAuth endpoints
|
||||
15. ⏳ Optimize token handling
|
||||
16. ⏳ Update documentation
|
||||
|
|
@ -322,16 +344,19 @@ Detailed documentation available:
|
|||
If you encounter issues:
|
||||
|
||||
1. **Check logs:**
|
||||
|
||||
- Foundation callback logs (Vercel deployment)
|
||||
- Token exchange errors
|
||||
- Profile sync failures
|
||||
|
||||
2. **Verify environment:**
|
||||
|
||||
- VITE_FOUNDATION_URL is correct
|
||||
- FOUNDATION_OAUTH_CLIENT_SECRET is correct
|
||||
- Foundation service is running
|
||||
|
||||
3. **Test manually:**
|
||||
|
||||
- Use curl to test token endpoint
|
||||
- Check database for user profiles
|
||||
- Inspect cookies in browser
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ FOUNDATION_OAUTH_CLIENT_SECRET=<secret-from-foundation-setup>
|
|||
```
|
||||
|
||||
**Files affected:**
|
||||
|
||||
- `.env.foundation-oauth.example` - Example configuration
|
||||
|
||||
---
|
||||
|
|
@ -68,11 +69,13 @@ FOUNDATION_OAUTH_CLIENT_SECRET=<secret-from-foundation-setup>
|
|||
New utility modules for Foundation OAuth:
|
||||
|
||||
**Files created:**
|
||||
|
||||
- `code/client/lib/foundation-oauth.ts` - OAuth flow helpers
|
||||
- `code/client/lib/foundation-auth.ts` - Token/profile management
|
||||
- `code/client/hooks/use-foundation-auth.ts` - React hooks for auth handling
|
||||
|
||||
**Key functions:**
|
||||
|
||||
- `initiateFoundationLogin()` - Redirects to Foundation
|
||||
- `exchangeCodeForToken()` - Backend token exchange
|
||||
- `fetchUserProfileFromFoundation()` - Get user data from Foundation
|
||||
|
|
@ -84,10 +87,12 @@ New utility modules for Foundation OAuth:
|
|||
### Step 3: Backend OAuth Endpoints ✅
|
||||
|
||||
**Files created:**
|
||||
|
||||
- `code/api/auth/foundation-callback.ts` - Handles redirect from Foundation
|
||||
- `code/api/auth/exchange-token.ts` - Token exchange endpoint
|
||||
|
||||
**Flow:**
|
||||
|
||||
1. User clicks "Login with Foundation" on aethex.dev/login
|
||||
2. Browser redirected to `aethex.foundation/api/oauth/authorize`
|
||||
3. User authenticates on Foundation
|
||||
|
|
@ -101,14 +106,17 @@ New utility modules for Foundation OAuth:
|
|||
### Step 4: Frontend Login Page Refactoring ✅
|
||||
|
||||
**File modified:**
|
||||
|
||||
- `code/client/pages/Login.tsx`
|
||||
|
||||
**Changes:**
|
||||
|
||||
- Replaced local Discord OAuth button with "Login with Foundation" button
|
||||
- Uses `initiateFoundationLogin()` to start OAuth flow
|
||||
- Removed Discord Activity check for Discord login (now handled by Foundation)
|
||||
|
||||
**New UI:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Sign In to AeThex │
|
||||
|
|
@ -133,6 +141,7 @@ New utility modules for Foundation OAuth:
|
|||
### Step 5: Remove Old Authentication Endpoints
|
||||
|
||||
**Files to remove or deprecate:**
|
||||
|
||||
- `code/api/discord/oauth/start.ts` - Local Discord OAuth start
|
||||
- `code/api/discord/oauth/callback.ts` - Local Discord OAuth callback
|
||||
- `code/api/discord/link.ts` - Discord linking endpoint
|
||||
|
|
@ -151,11 +160,11 @@ After Foundation OAuth callback, sessions work as follows:
|
|||
|
||||
```javascript
|
||||
// Frontend makes authenticated requests:
|
||||
const response = await fetch('/api/user/profile', {
|
||||
const response = await fetch("/api/user/profile", {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${getFoundationAccessToken()}`
|
||||
Authorization: `Bearer ${getFoundationAccessToken()}`,
|
||||
},
|
||||
credentials: 'include' // Send cookies
|
||||
credentials: "include", // Send cookies
|
||||
});
|
||||
|
||||
// Backend validates token:
|
||||
|
|
@ -166,6 +175,7 @@ const response = await fetch('/api/user/profile', {
|
|||
```
|
||||
|
||||
**Files that need updates:**
|
||||
|
||||
- `code/server/index.ts` - Add Foundation token validation middleware
|
||||
- `code/api/_supabase.ts` - Support Foundation token context
|
||||
|
||||
|
|
@ -224,11 +234,13 @@ START: User visits aethex.dev/login
|
|||
After Phase 1 (Foundation setup) is complete, you'll receive:
|
||||
|
||||
1. **Foundation OAuth Details:**
|
||||
|
||||
- OAuth endpoint URLs (authorize, token)
|
||||
- Client ID: `aethex-corp`
|
||||
- Client Secret: (provide to FOUNDATION_OAUTH_CLIENT_SECRET env var)
|
||||
|
||||
2. **Foundation API Endpoints:**
|
||||
|
||||
- GET `/api/auth/me` - Get authenticated user profile
|
||||
- POST `/api/oauth/authorize` - Authorization endpoint
|
||||
- POST `/api/oauth/token` - Token exchange endpoint
|
||||
|
|
@ -253,12 +265,14 @@ After Phase 1 (Foundation setup) is complete, you'll receive:
|
|||
### Local Testing
|
||||
|
||||
1. **Set up environment:**
|
||||
|
||||
```bash
|
||||
export VITE_FOUNDATION_URL=http://localhost:3001 # or staging URL
|
||||
export FOUNDATION_OAUTH_CLIENT_SECRET=<test-secret>
|
||||
```
|
||||
|
||||
2. **Test login flow:**
|
||||
|
||||
- Visit `http://localhost:5173/login`
|
||||
- Click "Login with Foundation"
|
||||
- Should redirect to Foundation auth page
|
||||
|
|
@ -340,16 +354,19 @@ After Phase 3 stabilizes:
|
|||
### Common Issues
|
||||
|
||||
**Issue: "Authorization code not received"**
|
||||
|
||||
- Check redirect_uri matches registered value
|
||||
- Verify client_id=aethex-corp in Foundation
|
||||
- Check Foundation environment is accessible
|
||||
|
||||
**Issue: "Token exchange failed"**
|
||||
|
||||
- Verify FOUNDATION_OAUTH_CLIENT_SECRET is correct
|
||||
- Check Foundation token endpoint is accessible
|
||||
- Review Foundation logs for errors
|
||||
|
||||
**Issue: "User profile not syncing"**
|
||||
|
||||
- Verify Supabase connection
|
||||
- Check user_profiles table exists locally
|
||||
- Review foundation-callback logs
|
||||
|
|
@ -365,6 +382,7 @@ After Phase 3 stabilizes:
|
|||
## Code References
|
||||
|
||||
**New files:**
|
||||
|
||||
- `code/client/lib/foundation-oauth.ts`
|
||||
- `code/client/lib/foundation-auth.ts`
|
||||
- `code/client/hooks/use-foundation-auth.ts`
|
||||
|
|
@ -372,9 +390,11 @@ After Phase 3 stabilizes:
|
|||
- `code/api/auth/exchange-token.ts`
|
||||
|
||||
**Modified files:**
|
||||
|
||||
- `code/client/pages/Login.tsx` - OAuth flow updated
|
||||
|
||||
**Deprecated (to remove):**
|
||||
|
||||
- `code/api/discord/oauth/start.ts`
|
||||
- `code/api/discord/oauth/callback.ts`
|
||||
- `code/api/discord/link.ts`
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
Before running tests, ensure:
|
||||
|
||||
1. **Environment variables are set:**
|
||||
|
||||
```bash
|
||||
VITE_FOUNDATION_URL=https://aethex.foundation # or staging/localhost
|
||||
FOUNDATION_OAUTH_CLIENT_SECRET=<received-from-foundation>
|
||||
|
|
@ -12,6 +13,7 @@ Before running tests, ensure:
|
|||
```
|
||||
|
||||
2. **Foundation is operational:**
|
||||
|
||||
- aethex.foundation is running
|
||||
- OAuth endpoints are accessible
|
||||
- Test user accounts exist
|
||||
|
|
@ -30,11 +32,13 @@ Before running tests, ensure:
|
|||
**Objective:** Verify the login page displays Foundation OAuth button
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `http://localhost:5173/login` (or prod URL)
|
||||
2. Look for "Login with Foundation" button
|
||||
3. Verify button is visible and clickable
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
✓ Login page displays
|
||||
✓ "Login with Foundation" button visible
|
||||
|
|
@ -51,11 +55,13 @@ Before running tests, ensure:
|
|||
**Objective:** Verify clicking the button redirects to Foundation
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. On login page, click "Login with Foundation" button
|
||||
2. Observe browser URL change
|
||||
3. Check redirect parameters
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
Redirected to:
|
||||
https://aethex.foundation/api/oauth/authorize
|
||||
|
|
@ -75,12 +81,14 @@ https://aethex.foundation/api/oauth/authorize
|
|||
**Objective:** User authenticates on Foundation
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. You're now on Foundation login page
|
||||
2. Enter test credentials
|
||||
3. If prompted, grant aethex.dev permissions
|
||||
4. Click "Authorize" or similar
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
✓ Foundation accepts credentials
|
||||
✓ Permission screen appears (if configured)
|
||||
|
|
@ -96,11 +104,13 @@ https://aethex.foundation/api/oauth/authorize
|
|||
**Objective:** Verify Foundation redirects back with authorization code
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. After Foundation authentication completes
|
||||
2. Observe browser URL change
|
||||
3. Look for authorization code in URL
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
Browser redirects to:
|
||||
https://aethex.dev/api/auth/foundation-callback
|
||||
|
|
@ -121,11 +131,13 @@ Check browser console:
|
|||
**Objective:** Backend exchanges code for access token
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Monitor network requests in browser Dev Tools
|
||||
2. Look for POST to `/api/auth/exchange-token`
|
||||
3. Check response status
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
Network:
|
||||
POST /api/auth/exchange-token
|
||||
|
|
@ -154,11 +166,13 @@ Cookies set:
|
|||
**Objective:** Verify user profile created/updated in local database
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. After successful login, check database
|
||||
2. Query user_profiles table
|
||||
3. Verify user exists with correct data
|
||||
|
||||
**Database Query:**
|
||||
|
||||
```sql
|
||||
-- Check user was created/updated
|
||||
SELECT id, email, username, profile_completed, updated_at
|
||||
|
|
@ -184,11 +198,13 @@ LIMIT 1;
|
|||
**Objective:** User redirected to dashboard after authentication
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. After token exchange and profile sync
|
||||
2. Browser should automatically redirect
|
||||
3. Check final URL
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
Browser URL: https://aethex.dev/dashboard
|
||||
✓ Dashboard loads successfully
|
||||
|
|
@ -205,23 +221,26 @@ Browser URL: https://aethex.dev/dashboard
|
|||
**Objective:** User can make authenticated API calls
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. On authenticated dashboard
|
||||
2. Use browser console to test:
|
||||
|
||||
```javascript
|
||||
const token = document.cookie
|
||||
.split(';')
|
||||
.find(c => c.trim().startsWith('foundation_access_token='))
|
||||
?.split('=')[1];
|
||||
|
||||
fetch('/api/user/profile', {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
credentials: 'include'
|
||||
.split(";")
|
||||
.find((c) => c.trim().startsWith("foundation_access_token="))
|
||||
?.split("=")[1];
|
||||
|
||||
fetch("/api/user/profile", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
credentials: "include",
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(console.log);
|
||||
.then((r) => r.json())
|
||||
.then(console.log);
|
||||
```
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```javascript
|
||||
// Console output:
|
||||
{
|
||||
|
|
@ -241,12 +260,14 @@ Browser URL: https://aethex.dev/dashboard
|
|||
**Objective:** Verify logout clears Foundation auth
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. On authenticated dashboard
|
||||
2. Click logout/settings
|
||||
3. Trigger logout action
|
||||
4. Verify redirect to login
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
✓ Logout triggered
|
||||
✓ Cookies cleared:
|
||||
|
|
@ -256,11 +277,13 @@ Browser URL: https://aethex.dev/dashboard
|
|||
✓ Previous authenticated state lost
|
||||
```
|
||||
|
||||
**Test command (if logout has UI):
|
||||
\*\*Test command (if logout has UI):
|
||||
|
||||
```javascript
|
||||
// Clear cookies manually in console
|
||||
document.cookie = 'foundation_access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC;';
|
||||
document.cookie = 'auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;';
|
||||
document.cookie =
|
||||
"foundation_access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
|
||||
document.cookie = "auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
|
||||
```
|
||||
|
||||
**Success Criteria:** ✅ Cookies cleared, session terminated
|
||||
|
|
@ -272,6 +295,7 @@ document.cookie = 'auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;';
|
|||
**Objective:** Verify redirect works when accessing protected page first
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Logout (or clear cookies)
|
||||
2. Visit protected page: `http://localhost:5173/dashboard?next=/admin`
|
||||
3. Get redirected to login
|
||||
|
|
@ -279,6 +303,7 @@ document.cookie = 'auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;';
|
|||
5. After auth, should redirect to `/admin` instead of `/dashboard`
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
✓ Initial redirect to /login with ?next=/admin
|
||||
✓ After Foundation auth, redirected to /admin
|
||||
|
|
@ -293,10 +318,12 @@ document.cookie = 'auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;';
|
|||
### Error 1: Invalid Authorization Code
|
||||
|
||||
**How to trigger:**
|
||||
|
||||
1. Manually modify URL code parameter: `?code=invalid_code`
|
||||
2. Let callback process
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
Error: token_exchange
|
||||
Message: Failed to exchange authorization code
|
||||
|
|
@ -310,10 +337,12 @@ Redirect to: /login?error=token_exchange
|
|||
### Error 2: Missing Client Secret
|
||||
|
||||
**How to trigger:**
|
||||
|
||||
1. Unset `FOUNDATION_OAUTH_CLIENT_SECRET` env var
|
||||
2. Attempt login
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
Error: 500 or token_exchange error
|
||||
Message: Missing environment variables
|
||||
|
|
@ -327,11 +356,13 @@ Redirect to: /login with error
|
|||
### Error 3: Foundation Unavailable
|
||||
|
||||
**How to trigger:**
|
||||
|
||||
1. Stop Foundation service
|
||||
2. Attempt login
|
||||
3. Foundation authorize redirects back
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
Error: Token exchange fails
|
||||
Message: Failed to connect to Foundation
|
||||
|
|
@ -345,10 +376,12 @@ Redirect to: /login with error message
|
|||
### Error 4: Expired Authorization Code
|
||||
|
||||
**How to trigger:**
|
||||
|
||||
1. Wait >10 minutes after Foundation redirect
|
||||
2. Complete the callback
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
```
|
||||
Error: invalid_grant or code_expired
|
||||
Message: Authorization code has expired
|
||||
|
|
@ -371,6 +404,7 @@ Test on multiple browsers:
|
|||
- [ ] Mobile Safari
|
||||
|
||||
**Checklist for each browser:**
|
||||
|
||||
- [ ] Login page renders correctly
|
||||
- [ ] Redirect to Foundation works
|
||||
- [ ] Cookies are set (check Dev Tools)
|
||||
|
|
@ -459,18 +493,18 @@ Environment: [Staging/Production]
|
|||
|
||||
## Test Results
|
||||
|
||||
| Test | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Test 1: Login Page | ✅/❌ | |
|
||||
| Test 2: Redirect | ✅/❌ | |
|
||||
| Test 3: Foundation Auth | ✅/❌ | |
|
||||
| Test 4: Callback | ✅/❌ | |
|
||||
| Test 5: Token Exchange | ✅/❌ | |
|
||||
| Test 6: Profile Sync | ✅/❌ | |
|
||||
| Test 7: Dashboard | ✅/❌ | |
|
||||
| Test 8: API Requests | ✅/❌ | |
|
||||
| Test 9: Logout | ✅/❌ | |
|
||||
| Test 10: Redirects | ✅/❌ | |
|
||||
| Test | Status | Notes |
|
||||
| ----------------------- | ------ | ----- |
|
||||
| Test 1: Login Page | ✅/❌ | |
|
||||
| Test 2: Redirect | ✅/❌ | |
|
||||
| Test 3: Foundation Auth | ✅/❌ | |
|
||||
| Test 4: Callback | ✅/❌ | |
|
||||
| Test 5: Token Exchange | ✅/❌ | |
|
||||
| Test 6: Profile Sync | ✅/❌ | |
|
||||
| Test 7: Dashboard | ✅/❌ | |
|
||||
| Test 8: API Requests | ✅/❌ | |
|
||||
| Test 9: Logout | ✅/❌ | |
|
||||
| Test 10: Redirects | ✅/❌ | |
|
||||
|
||||
## Errors Encountered
|
||||
|
||||
|
|
@ -503,15 +537,18 @@ Environment: [Staging/Production]
|
|||
### Key Metrics to Monitor
|
||||
|
||||
1. **Authentication Success Rate**
|
||||
|
||||
- Should be >99%
|
||||
- Track failed logins
|
||||
|
||||
2. **Error Categories**
|
||||
|
||||
- Code exchange failures
|
||||
- Token validation failures
|
||||
- Profile sync failures
|
||||
|
||||
3. **Performance**
|
||||
|
||||
- Token exchange time (target <2s)
|
||||
- Dashboard load time after auth
|
||||
- API request latency
|
||||
|
|
@ -524,6 +561,7 @@ Environment: [Staging/Production]
|
|||
### Alert Thresholds
|
||||
|
||||
Set alerts for:
|
||||
|
||||
- Auth failure rate > 5%
|
||||
- Token exchange time > 5 seconds
|
||||
- Foundation connectivity issues
|
||||
|
|
@ -534,6 +572,7 @@ Set alerts for:
|
|||
## Rollback Triggers
|
||||
|
||||
Immediately rollback if:
|
||||
|
||||
- Auth failure rate > 25%
|
||||
- Unable to authenticate any new users
|
||||
- Data corruption in user_profiles
|
||||
|
|
|
|||
Loading…
Reference in a new issue