-
Notifications
You must be signed in to change notification settings - Fork 3
Add account creation to Vortex #912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ebma
wants to merge
91
commits into
staging
Choose a base branch
from
882-account-creation-on-vortex
base: staging
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
91 commits
Select commit
Hold shift + click to select a range
5bcb1be
Create architecture docs for supabase auth implementation
ebma 707d11d
Implement Supabase authentication routes and user ID tracking in models
ebma 91fbb4a
Add user model and establish associations with other entities
ebma d649c08
Add initial configuration files and scripts for project setup
ebma 531564f
Implement authentication flow with email verification and OTP handling
ebma 82e1cd8
Add email and OTP authentication steps to the user flow
ebma 693cff7
Merge branch 'staging' into 882-account-creation-on-vortex
ebma eee8f73
Update @supabase/supabase-js dependency to use catalog version
ebma f88ea27
Add userId to registerRamp functionality for enhanced user tracking
ebma b30a9a8
Fix tax_ids migration to safely rename column and add enum value
ebma f8a1670
Add userId extraction and handling in quote and ramp routes for impro…
ebma de2e6bf
Refactor useAuthTokens to accept actorRef and update ramp machine for…
ebma 063ac40
Fix quote not generated if user not logged in
ebma 7cc19e9
Fix email change button not working
ebma b7ed019
Fix session refresh not working
ebma 5c2443e
Rename authTokens to userSessionTokens
ebma 8b1af39
Move auth before details page
ebma 267cdca
Fix initial auth state
ebma b0cf245
Change stepper
ebma e328411
Adjust styling of auth cards
ebma 8bf845b
Add logout feature to details page
ebma a1c28f5
Fix email not stored
ebma 5a912d4
Adjust magic link template
ebma 06f7958
Adjust magic link template again
ebma a52726d
Make quote summary always expand over card content itself
ebma b005ecb
Make quote summary also overlap on other cards
ebma 54aa972
Adjust spacing of card content
ebma 260935f
Refactor layout and improve z-index for QuoteSummary in multiple comp…
ebma e16f378
Refactor to deduplicate
ebma 0834101
Merge branch 'staging' into 882-account-creation-on-vortex
ebma 901733e
Add revertMigration function to handle specific migration rollbacks
ebma a6f8d0e
Adjust migrations to properly apply to existing data
ebma b1d23bf
Add proxy to migrator script
ebma 8841ca9
Make AuthOTPStep responsive
ebma 7f48fac
Fix issue with otp fields not cleared on second error
ebma 44de0e3
Add signup email template
ebma 5cea2fa
Adjust magic_link.html template
ebma c9e82d0
Adjust 022-add-user-id-to-entities.ts to prevent losing data on down()
ebma 133a5ff
Fix other bug in 022 migration
ebma c1c0ae0
Fix type issues
ebma f330aa6
Use cn()
ebma 41c4482
Remove userSessionTokens from ramp machine
ebma a130238
Refactor layout by removing unnecessary overflow-y-auto classes in Av…
ebma 32ae0cb
Make sure quote is persisted on rampState machine
ebma d6886d9
Try to fix the spacing between action button and quote summary
ebma 12de1b8
Use different method to position widget action and quote summary
ebma f251695
Fix access to userId from requests
ebma cf59dae
Allow rampState userId to be null on database model
ebma e24ed22
Adjust the spacing below the actions button
ebma 15b845c
Fix stepper phases
ebma ffb2a70
Remove automatic scroll on quote summary
ebma eaae3e5
Fix issue with quote not set on ramp state when starting on email flow
ebma c8e91a1
Fix action button alignment in AveniaLivenessStep
ebma daa2475
Rename 'users' table to 'profiles' to adhere to Supabase best practice
ebma 5197de3
Merge architecture documents for auth feature
ebma 7211e32
Initial plan
Copilot 5f979be
Address code review comments: logging, PostgreSQL compatibility, rate…
Copilot b8125dd
Fix code review issues: truncate auth header in logs, use ref to prev…
Copilot 8a8169c
Refactor URL token handling in authentication flow to set Supabase se…
ebma 20d0c5f
Refactor email input form structure and styling for improved accessib…
ebma ed92096
Replace console log with logger
ebma 22671a6
Refactor authentication token handling to use camelCase properties fo…
ebma ca868ef
Configure supabase signup template in config
ebma 70568bd
Merge pull request #969 from pendulum-chain/copilot/sub-pr-912
ebma 2fbed83
Add doc for supabase auth
ebma 64697b0
Merge branch 'staging' into 882-account-creation-on-vortex
ebma 95d2f33
Fix type issues after merge
ebma 54281ec
Fix email not set correctly on ramp state
ebma d2de645
Fix issue with local templates
ebma 1e969d4
Enhance authentication middleware and enforce user authentication for…
ebma 91ac142
Add checkbox for t&c
ebma f703c9d
Refactor KYC user ID handling to allow null values and update routes …
ebma a8f8592
Allow continuing in 'Enter details' page even if quote expired
ebma aec2a45
Merge branch 'staging' into 882-account-creation-on-vortex
ebma c70f78d
Add more translations for other widget cards
ebma 0999560
Fix user can't proceed on 'enter details' page if quote expired
ebma fc797bd
Change go-back behaviour on payment summary to go back to 'enter deta…
ebma e5480d0
Remove comments
ebma 35b6b07
implement InputOTP
Sharqiewicz 9175421
Merge branch '882-account-creation-on-vortex' of github.com:pendulum-…
Sharqiewicz 17dc7d5
unify styles of email input
Sharqiewicz 8730b25
lint fixes
Sharqiewicz 484108d
improve email validation
Sharqiewicz 652d111
improve email validation
Sharqiewicz 2ee1d77
simplify QuoteSummary animations
Sharqiewicz ed122c6
fix partnerApiKeys type issue
Sharqiewicz 6416dd1
remove unused shadcn config
Sharqiewicz 2f2b0d7
lint fixes
Sharqiewicz 075ef08
remove unused vars
Sharqiewicz 849a5ad
remove duplicated inline style for quote summary
Sharqiewicz ac28722
change quote summary layout height
Sharqiewicz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| import { Request, Response } from "express"; | ||
| import User from "../../models/user.model"; | ||
| import { SupabaseAuthService } from "../services/auth"; | ||
|
|
||
| export class AuthController { | ||
| /** | ||
| * Check if email is registered | ||
| * GET /api/v1/auth/check-email?email=user@example.com | ||
| */ | ||
| static async checkEmail(req: Request, res: Response) { | ||
| try { | ||
| const { email } = req.query; | ||
|
|
||
| if (!email || typeof email !== "string") { | ||
| return res.status(400).json({ | ||
| error: "Email is required" | ||
| }); | ||
| } | ||
|
|
||
| const exists = await SupabaseAuthService.checkUserExists(email); | ||
|
|
||
| return res.json({ | ||
| action: exists ? "signin" : "signup", | ||
| exists | ||
| }); | ||
| } catch (error) { | ||
| console.error("Error in checkEmail:", error); | ||
| return res.status(500).json({ | ||
| error: "Failed to check email" | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Request OTP | ||
| * POST /api/v1/auth/request-otp | ||
| */ | ||
| static async requestOTP(req: Request, res: Response) { | ||
| try { | ||
| const { email } = req.body; | ||
|
|
||
| if (!email) { | ||
| return res.status(400).json({ | ||
| error: "Email is required" | ||
| }); | ||
| } | ||
|
|
||
| await SupabaseAuthService.sendOTP(email); | ||
|
|
||
| return res.json({ | ||
| message: "OTP sent to email", | ||
| success: true | ||
| }); | ||
| } catch (error) { | ||
| console.error("Error in requestOTP:", error); | ||
| return res.status(500).json({ | ||
| error: "Failed to send OTP" | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Verify OTP | ||
| * POST /api/v1/auth/verify-otp | ||
| */ | ||
| static async verifyOTP(req: Request, res: Response) { | ||
| try { | ||
| const { email, token } = req.body; | ||
|
|
||
| if (!email || !token) { | ||
| return res.status(400).json({ | ||
| error: "Email and token are required" | ||
| }); | ||
| } | ||
|
|
||
| const result = await SupabaseAuthService.verifyOTP(email, token); | ||
|
|
||
| // Sync user to local database (upsert) | ||
| await User.upsert({ | ||
| email: email, | ||
| id: result.user_id | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the |
||
| }); | ||
|
|
||
| return res.json({ | ||
| access_token: result.access_token, | ||
| refresh_token: result.refresh_token, | ||
| success: true, | ||
| user_id: result.user_id | ||
| }); | ||
| } catch (error) { | ||
| console.error("Error in verifyOTP:", error); | ||
| return res.status(400).json({ | ||
| error: "Invalid OTP or OTP expired" | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Refresh token | ||
| * POST /api/v1/auth/refresh | ||
| */ | ||
| static async refreshToken(req: Request, res: Response) { | ||
| try { | ||
| const { refresh_token } = req.body; | ||
|
|
||
| if (!refresh_token) { | ||
| return res.status(400).json({ | ||
| error: "Refresh token is required" | ||
| }); | ||
| } | ||
|
|
||
| const result = await SupabaseAuthService.refreshToken(refresh_token); | ||
|
|
||
| return res.json({ | ||
| access_token: result.access_token, | ||
| refresh_token: result.refresh_token, | ||
| success: true | ||
| }); | ||
| } catch (error) { | ||
| console.error("Error in refreshToken:", error); | ||
| return res.status(401).json({ | ||
| error: "Invalid refresh token" | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Verify token | ||
| * POST /api/v1/auth/verify | ||
| */ | ||
| static async verifyToken(req: Request, res: Response) { | ||
| try { | ||
| const { access_token } = req.body; | ||
|
|
||
| if (!access_token) { | ||
| return res.status(400).json({ | ||
| error: "Access token is required" | ||
| }); | ||
| } | ||
|
|
||
| const result = await SupabaseAuthService.verifyToken(access_token); | ||
|
|
||
| if (!result.valid) { | ||
| return res.status(401).json({ | ||
| error: "Invalid token", | ||
| valid: false | ||
| }); | ||
| } | ||
|
|
||
| return res.json({ | ||
| user_id: result.user_id, | ||
| valid: true | ||
| }); | ||
| } catch (error) { | ||
| console.error("Error in verifyToken:", error); | ||
| return res.status(401).json({ | ||
| error: "Token verification failed", | ||
| valid: false | ||
| }); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import { NextFunction, Request, Response } from "express"; | ||
| import logger from "../../config/logger"; | ||
| import { SupabaseAuthService } from "../services/auth"; | ||
|
|
||
| declare global { | ||
| namespace Express { | ||
| interface Request { | ||
| userId?: string; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Middleware to verify Supabase auth token and attach userId to request | ||
| */ | ||
| export async function requireAuth(req: Request, res: Response, next: NextFunction) { | ||
| try { | ||
| const authHeader = req.headers.authorization; | ||
|
|
||
| if (!authHeader?.startsWith("Bearer ")) { | ||
| return res.status(401).json({ | ||
| error: "Missing or invalid authorization header" | ||
| }); | ||
| } | ||
|
|
||
| const token = authHeader.substring(7); | ||
| const result = await SupabaseAuthService.verifyToken(token); | ||
|
|
||
| if (!result.valid) { | ||
| return res.status(401).json({ | ||
| error: "Invalid or expired token" | ||
| }); | ||
| } | ||
|
|
||
| req.userId = result.user_id; | ||
| next(); | ||
| } catch (error) { | ||
| console.error("Auth middleware error:", error); | ||
| return res.status(401).json({ | ||
| error: "Authentication failed" | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Optional auth - attaches userId if token present | ||
| */ | ||
| export async function optionalAuth(req: Request, res: Response, next: NextFunction) { | ||
| try { | ||
| const authHeader = req.headers.authorization; | ||
|
|
||
| if (authHeader?.startsWith("Bearer ")) { | ||
| const token = authHeader.substring(7); | ||
| const result = await SupabaseAuthService.verifyToken(token); | ||
|
|
||
| if (result.valid) { | ||
| req.userId = result.user_id; | ||
| } | ||
| } | ||
|
|
||
| next(); | ||
| } catch (error) { | ||
ebma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Log truncated token for security - only show first/last few characters | ||
| const authHeader = req.headers.authorization; | ||
| const truncatedAuth = authHeader | ||
| ? `${authHeader.substring(0, 15)}...${authHeader.substring(authHeader.length - 4)}` | ||
| : undefined; | ||
|
|
||
| logger.warn("optionalAuth middleware: authentication error", { | ||
| authorization: truncatedAuth, | ||
| error, | ||
| path: req.path | ||
| }); | ||
| next(); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { Router } from "express"; | ||
| import { AuthController } from "../../controllers/auth.controller"; | ||
|
|
||
| const router = Router(); | ||
|
|
||
| router.get("/check-email", AuthController.checkEmail); | ||
| router.post("/request-otp", AuthController.requestOTP); | ||
| router.post("/verify-otp", AuthController.verifyOTP); | ||
| router.post("/refresh", AuthController.refreshToken); | ||
| router.post("/verify", AuthController.verifyToken); | ||
|
|
||
| export default router; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this operation is stateless on the backend, right? Maybe we could add an entry to the users table, to mark the last OTP timestamp or any other metadata.
In case we loose the state in the UI or we want to keep track.