DEVELOPMENT ENVIRONMENT

~liljamo/tixe

ref: 0.1.1 tixe/handlers/auth.go -rw-r--r-- 2.9 KiB
122737fdJonni Liljamo feat: maybe possibly only build on tags? 11 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
 * more information.
 */
package handlers

import (
	"context"
	"log"
	"net/http"
	"tixe/auth"
	"tixe/db"
	"tixe/types"
	"tixe/util"

	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"github.com/jackc/pgx/v5"
	"github.com/oklog/ulid/v2"
)

func AuthCallback(auth *auth.Auth) gin.HandlerFunc {
	return func(c *gin.Context) {
		session := sessions.Default(c)
		if c.Query("state") != session.Get("state") {
			errStr := "Invalid state parameter"
			util.RenderError(c, "state error", errStr, nil)
			return
		}

		token, err := auth.Exchange(c.Request.Context(), c.Query("code"))
		if err != nil {
			errStr := "Failed to exchange authorization code for token"
			util.RenderError(c, "token error", errStr, nil)
			return
		}

		idToken, err := auth.VerifyIDToken(c.Request.Context(), token)
		if err != nil {
			errStr := "Failed to verify ID token"
			util.RenderError(c, "token error", errStr, nil)
			return
		}

		var profile map[string]interface{}
		if err := idToken.Claims(&profile); err != nil {
			errStr := "Failed to get claims"
			util.RenderError(c, "claims error", errStr, nil)
			return
		}

		// Try to get the relevant details of the user
		var userId, userDisplayName string
		// idToken.Subject should be unique and should not change.
		// I think. Maybe. Possibly. Hopefully.
		scanErr := db.PgPool.QueryRow(context.Background(),
			"SELECT id, display_name FROM users WHERE oidc_subject = $1", idToken.Subject).Scan(&userId, &userDisplayName)
		if scanErr == pgx.ErrNoRows {
			log.Printf("[tixe/auth] New user detected logging in, creating entry in database")
			// The user does not exist in the db, create it
			userId = ulid.Make().String()
			userDisplayName = profile["name"].(string)
			_, err = db.PgPool.Exec(context.Background(),
				"INSERT INTO users(id, display_name, oidc_subject) VALUES($1, $2, $3)",
				userId, userDisplayName, idToken.Subject)
			if err != nil {
				errStr := "Could not create database entry for oidc user"
				log.Printf("[tixe/auth] ERROR: %s", errStr)
				util.RenderError(c, "db error", errStr, nil)
				return
			}
		} else if scanErr != nil {
			// scanErr was some other error than ErrNoRows
			errStr := "Could not query database for oidc user"
			log.Printf("[tixe/auth] ERROR: %s: %v", errStr, scanErr)
			util.RenderError(c, "db error", errStr, nil)
			return
		}

		// The user.Id field is read in other requests to read user data from the db
		session.Set("user", types.User { Id: userId, DisplayName: userDisplayName })
		session.Set("access_token", token.AccessToken)
		session.Set("profile", profile)
		if err := session.Save(); err != nil {
			errStr := "Failed to save session"
			log.Printf("[tixe/auth] ERROR: %s: %v", errStr, err)
			util.RenderError(c, "session error", errStr, nil)
			return
		}

		c.Redirect(http.StatusTemporaryRedirect, "/")
	}
}