aboutsummaryrefslogtreecommitdiff
path: root/backend/internal/server/auth
diff options
context:
space:
mode:
Diffstat (limited to 'backend/internal/server/auth')
-rw-r--r--backend/internal/server/auth/callback/callback.go93
-rw-r--r--backend/internal/server/auth/login/login.go28
-rw-r--r--backend/internal/server/auth/user/user.go45
3 files changed, 166 insertions, 0 deletions
diff --git a/backend/internal/server/auth/callback/callback.go b/backend/internal/server/auth/callback/callback.go
new file mode 100644
index 0000000..f0a3413
--- /dev/null
+++ b/backend/internal/server/auth/callback/callback.go
@@ -0,0 +1,93 @@
+package callback
+
+import (
+ "context"
+ "log/slog"
+ "net/http"
+ "time"
+
+ "ibd-trader/internal/auth"
+ "ibd-trader/internal/database"
+ "ibd-trader/internal/server/middleware"
+)
+
+func Handler(
+ logger *slog.Logger,
+ userStore database.UserStore,
+ sessionStore database.SessionStore,
+ auth *auth.Authenticator,
+) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ // Timeout callback operations after 10 seconds
+ ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
+ defer cancel()
+
+ // Check state
+ state := r.URL.Query().Get("state")
+ if state == "" {
+ http.Error(w, "No state provided", http.StatusBadRequest)
+ return
+ }
+
+ exists, err := sessionStore.CheckState(ctx, state)
+ if err != nil {
+ logger.ErrorContext(ctx, "Failed to check state", "error", err)
+ http.Error(w, "Failed to check state", http.StatusInternalServerError)
+ return
+ }
+ if !exists {
+ http.Error(w, "Invalid state", http.StatusBadRequest)
+ return
+ }
+
+ // Exchange code for token
+ token, err := auth.Exchange(ctx, r.URL.Query().Get("code"))
+ if err != nil {
+ logger.ErrorContext(ctx, "Failed to exchange code", "error", err)
+ http.Error(w, "Failed to exchange code", http.StatusUnauthorized)
+ return
+ }
+
+ // Verify token
+ idToken, err := auth.VerifyIDToken(ctx, token)
+ if err != nil {
+ logger.ErrorContext(ctx, "Failed to verify ID token", "error", err)
+ http.Error(w, "Failed to verify ID token", http.StatusInternalServerError)
+ return
+ }
+
+ // Add user to database
+ if err := userStore.AddUser(ctx, idToken.Subject); err != nil {
+ logger.ErrorContext(ctx, "Failed to add user", "error", err)
+ http.Error(w, "Failed to add user", http.StatusInternalServerError)
+ return
+ }
+
+ // Create session
+ session, err := sessionStore.CreateSession(ctx, token, idToken)
+ if err != nil {
+ logger.ErrorContext(ctx, "Failed to create session", "error", err)
+ http.Error(w, "Failed to create session", http.StatusInternalServerError)
+ return
+ }
+
+ // Set session cookie
+ http.SetCookie(w, &http.Cookie{
+ Name: middleware.SessionCookie,
+ Value: session,
+ Path: "/",
+ Domain: "",
+ Expires: token.Expiry,
+ RawExpires: "",
+ MaxAge: 0,
+ Secure: true,
+ HttpOnly: true,
+ SameSite: http.SameSiteLaxMode,
+ Raw: "",
+ Unparsed: nil,
+ })
+
+ // Redirect
+ http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
+ }
+}
diff --git a/backend/internal/server/auth/login/login.go b/backend/internal/server/auth/login/login.go
new file mode 100644
index 0000000..102e3d4
--- /dev/null
+++ b/backend/internal/server/auth/login/login.go
@@ -0,0 +1,28 @@
+package login
+
+import (
+ "context"
+ "log/slog"
+ "net/http"
+ "time"
+
+ "ibd-trader/internal/auth"
+ "ibd-trader/internal/database"
+)
+
+func Handler(logger *slog.Logger, store database.SessionStore, auth *auth.Authenticator) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ // Save state in session table w/o user id
+ ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
+ defer cancel()
+ state, err := store.CreateState(ctx)
+ if err != nil {
+ logger.ErrorContext(ctx, "Failed to create state", "error", err)
+ http.Error(w, "Failed to create state", http.StatusInternalServerError)
+ return
+ }
+
+ // Redirect to oauth provider
+ http.Redirect(w, r, auth.AuthCodeURL(state), http.StatusTemporaryRedirect)
+ }
+}
diff --git a/backend/internal/server/auth/user/user.go b/backend/internal/server/auth/user/user.go
new file mode 100644
index 0000000..526329d
--- /dev/null
+++ b/backend/internal/server/auth/user/user.go
@@ -0,0 +1,45 @@
+package user
+
+import (
+ "context"
+ "encoding/json"
+ "log/slog"
+ "net/http"
+ "time"
+
+ "ibd-trader/internal/auth"
+ "ibd-trader/internal/database"
+)
+
+func Handler(
+ logger *slog.Logger,
+ auth *auth.Authenticator,
+) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
+ defer cancel()
+
+ // Get session from context
+ session, ok := ctx.Value("session").(*database.Session)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Create token source
+ ts := auth.TokenSource(ctx, &session.OAuthToken)
+
+ // Get user info
+ userInfo, err := auth.UserInfo(ctx, ts)
+ if err != nil {
+ logger.ErrorContext(ctx, "Failed to get user info", "error", err)
+ http.Error(w, "Failed to get user info", http.StatusInternalServerError)
+ return
+ }
+
+ // Write user info to response
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ _ = json.NewEncoder(w).Encode(userInfo)
+ }
+}