Almost working auth

This commit is contained in:
Adrien Bouvais 2024-05-05 21:10:09 +02:00
parent 6382b6c02b
commit 8f28d07051
15 changed files with 238 additions and 63 deletions

View File

@ -11,7 +11,6 @@ import (
var edgeCtx context.Context var edgeCtx context.Context
var edgeClient *edgedb.Client var edgeClient *edgedb.Client
var CurrentUser User
type User struct { type User struct {
ID edgedb.UUID `edgedb:"id"` ID edgedb.UUID `edgedb:"id"`
@ -74,22 +73,15 @@ func init() {
log.Fatal(err) log.Fatal(err)
} }
// Get the user test@example.com // TODO Change
var user User edgeCtx = ctx
err = client.QuerySingle(ctx, ` var clientUUID edgedb.UUID
SELECT User { id, email, name, setting } FILTER .email = <str>$0 LIMIT 1 clientUUID, err = edgedb.ParseUUID("9323365e-0b09-11ef-8f41-c3575d386283")
`, &user, "test@example.com")
if err != nil { if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in init") fmt.Println("Error in edgedb.ParseUUID: in init")
log.Fatal(err) log.Fatal(err)
} }
CurrentUser = user edgeClient = client.WithGlobals(map[string]interface{}{"current_user_id": clientUUID})
fmt.Print("Current User: ")
fmt.Println(CurrentUser)
edgeCtx = ctx
edgeClient = client
} }
func getLastArea() edgedb.UUID { func getLastArea() edgedb.UUID {
@ -99,7 +91,7 @@ func getLastArea() edgedb.UUID {
filter .conversation.name = 'Default' AND .conversation.user.id = <uuid>$0 filter .conversation.name = 'Default' AND .conversation.user.id = <uuid>$0
order by .position desc order by .position desc
limit 1 limit 1
`, &inserted, CurrentUser.ID) `, &inserted, getCurrentUserID())
if err != nil { if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in getLastArea") fmt.Println("Error in edgedb.QuerySingle: in getLastArea")
log.Fatal(err) log.Fatal(err)
@ -107,21 +99,43 @@ func getLastArea() edgedb.UUID {
return inserted.id return inserted.id
} }
func getCurrentUserID() edgedb.UUID {
var result User
err := edgeClient.QuerySingle(edgeCtx, "SELECT global currentUser LIMIT 1;", &result)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in getCurrentUserID")
fmt.Println(err)
}
return result.ID
}
func checkIfLogin() bool {
var result User
err := edgeClient.QuerySingle(edgeCtx, "SELECT global currentUser LIMIT 1;", &result)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in checkIfLogin")
fmt.Println(err)
return false
}
return true
}
func insertArea() edgedb.UUID { func insertArea() edgedb.UUID {
// Insert a new area. // Insert a new area.
var inserted struct{ id edgedb.UUID } var inserted struct{ id edgedb.UUID }
err := edgeClient.QuerySingle(edgeCtx, ` err := edgeClient.QuerySingle(edgeCtx, `
WITH WITH
positionVar := count((SELECT Area FILTER .conversation.name = 'Default' AND .conversation.user.id = <uuid>$0)) + 1 positionVar := count((SELECT Area FILTER .conversation.name = 'Default' AND .conversation.user = global currentUser)) + 1
INSERT Area { INSERT Area {
position := positionVar, position := positionVar,
conversation := ( conversation := (
SELECT Conversation SELECT Conversation
FILTER .name = 'Default' AND .user.id = <uuid>$0 FILTER .name = 'Default' AND .user = global currentUser
LIMIT 1 LIMIT 1
) )
} }
`, &inserted, CurrentUser.ID) `, &inserted)
if err != nil { if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in insertArea") fmt.Println("Error in edgedb.QuerySingle: in insertArea")
log.Fatal(err) log.Fatal(err)
@ -144,11 +158,11 @@ func insertUserMessage(content string) edgedb.UUID {
), ),
conversation := ( conversation := (
SELECT Conversation SELECT Conversation
FILTER .name = 'Default' AND .user.id = <uuid>$3 FILTER .name = 'Default' AND .user = global currentUser
LIMIT 1 LIMIT 1
) )
} }
`, &inserted, "user", content, lastAreaID, CurrentUser.ID) `, &inserted, "user", content, lastAreaID)
if err != nil { if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in insertUserMessage") fmt.Println("Error in edgedb.QuerySingle: in insertUserMessage")
log.Fatal(err) log.Fatal(err)
@ -167,16 +181,16 @@ func insertBotMessage(content string, selected bool, model string) edgedb.UUID {
selected := <bool>$3, selected := <bool>$3,
conversation := ( conversation := (
SELECT Conversation SELECT Conversation
FILTER .name = 'Default' AND .user.id = <uuid>$4 FILTER .name = 'Default' AND .user = global currentUser
LIMIT 1 LIMIT 1
), ),
area := ( area := (
SELECT Area SELECT Area
FILTER .id = <uuid>$5 FILTER .id = <uuid>$4
LIMIT 1 LIMIT 1
) )
} }
`, &inserted, "bot", model, content, selected, CurrentUser.ID, lastAreaID) `, &inserted, "bot", model, content, selected, lastAreaID)
if err != nil { if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in insertBotMessage") fmt.Println("Error in edgedb.QuerySingle: in insertBotMessage")
log.Fatal(err) log.Fatal(err)
@ -185,6 +199,11 @@ func insertBotMessage(content string, selected bool, model string) edgedb.UUID {
} }
func getAllMessages() []Message { func getAllMessages() []Message {
// If no CurrentUser, return an empty array
if !checkIfLogin() {
return []Message{}
}
var messages []Message var messages []Message
err := edgeClient.Query(edgeCtx, ` err := edgeClient.Query(edgeCtx, `
@ -195,9 +214,9 @@ func getAllMessages() []Message {
role, role,
content, content,
date date
} FILTER .conversation.name = 'Default' AND .conversation.user.id = <uuid>$0 } FILTER .conversation.name = 'Default' AND .conversation.user = global currentUser
ORDER BY .date ASC ORDER BY .date ASC
`, &messages, CurrentUser.ID) `, &messages)
if err != nil { if err != nil {
fmt.Println("Error in edgedb.Query: in getAllMessages") fmt.Println("Error in edgedb.Query: in getAllMessages")
fmt.Println(err) fmt.Println(err)

View File

@ -1,8 +1,15 @@
using extension auth;
module default { module default {
global current_user_id: uuid;
global currentUser := (
select User
filter .id = global current_user_id
);
type User { type User {
required email: str;
required name: str;
required setting: Setting; required setting: Setting;
required identity: ext::auth::Identity;
} }
type Key { type Key {

View File

@ -0,0 +1,6 @@
CREATE MIGRATION m1frbbhs2tdrqsle67rgzwazozf3qp4xylmnepeacjodtdtcpmqzgq
ONTO m16dflw7c2tzuugatxe7g7ngx6ddn6eczk7a7a6oo5zlixscu565ta
{
CREATE EXTENSION pgcrypto VERSION '1.3';
CREATE EXTENSION auth VERSION '1.0';
};

View File

@ -0,0 +1,14 @@
CREATE MIGRATION m1ipsijwf3e65w6mvm2622aekxdgvd5hpdlej6mrn2twfbsh47s4mq
ONTO m1frbbhs2tdrqsle67rgzwazozf3qp4xylmnepeacjodtdtcpmqzgq
{
ALTER TYPE default::User {
CREATE REQUIRED LINK identity: ext::auth::Identity {
SET REQUIRED USING (<ext::auth::Identity>{});
};
};
CREATE GLOBAL default::currentUser := (SELECT
default::User
FILTER
(.identity ?= GLOBAL ext::auth::ClientTokenIdentity)
);
};

View File

@ -0,0 +1,8 @@
CREATE MIGRATION m1pknuisxcvpidw4gpdqip6qkiohucqgzmv3rcbli3dyvk2hlbmvaa
ONTO m1ipsijwf3e65w6mvm2622aekxdgvd5hpdlej6mrn2twfbsh47s4mq
{
ALTER TYPE default::User {
DROP PROPERTY email;
DROP PROPERTY name;
};
};

View File

@ -0,0 +1,10 @@
CREATE MIGRATION m1t2tddtq2grfxf4ldn6aj4yotvwiqclsrks6chobvs33d5mcd62mq
ONTO m1pknuisxcvpidw4gpdqip6qkiohucqgzmv3rcbli3dyvk2hlbmvaa
{
CREATE GLOBAL default::current_user_id -> std::uuid;
ALTER GLOBAL default::currentUser USING (SELECT
default::User
FILTER
(.id = GLOBAL default::current_user_id)
);
};

128
login.go Normal file
View File

@ -0,0 +1,128 @@
package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/gofiber/fiber/v2"
)
const EDGEDB_AUTH_BASE_URL = "http://127.0.0.1:10700/db/main/ext/auth"
func generatePKCE() (string, string) {
verifier := make([]byte, 32)
_, err := rand.Read(verifier)
if err != nil {
panic(err)
}
challenge := sha256.Sum256(verifier)
return base64.RawURLEncoding.EncodeToString(verifier), base64.RawURLEncoding.EncodeToString(challenge[:])
}
func handleUiSignIn(c *fiber.Ctx) error {
verifier, challenge := generatePKCE()
fmt.Println("handleUiSignIn verifier: " + verifier)
fmt.Println("handleUiSignIn challenge: " + challenge)
redirectURL := fmt.Sprintf("%s/ui/signin?challenge=%s", EDGEDB_AUTH_BASE_URL, challenge)
c.Cookie(&fiber.Cookie{
Name: "jade-edgedb-pkce-verifier",
Value: verifier,
HTTPOnly: true,
Path: "/",
Secure: true,
SameSite: "Strict",
})
return c.Redirect(redirectURL, fiber.StatusMovedPermanently)
}
func handleUiSignUp(c *fiber.Ctx) error {
verifier, challenge := generatePKCE()
fmt.Println("handleUiSignUp verifier: " + verifier)
fmt.Println("handleUiSignUp challenge: " + challenge)
redirectURL := fmt.Sprintf("%s/ui/signup?challenge=%s", EDGEDB_AUTH_BASE_URL, challenge)
c.Cookie(&fiber.Cookie{
Name: "jade-edgedb-pkce-verifier",
Value: verifier,
HTTPOnly: true,
Path: "/",
Secure: true,
SameSite: "Strict",
})
return c.Redirect(redirectURL, fiber.StatusMovedPermanently)
}
func handleCallbackSignup(c *fiber.Ctx) error {
code := c.Query("code")
fmt.Println("Callback signup code: " + code)
verifier := c.Cookies("jade-edgedb-pkce-verifier")
if verifier == "" {
return c.Status(fiber.StatusBadRequest).SendString("Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?")
}
return c.SendString("OK")
}
func handleCallback(c *fiber.Ctx) error {
code := c.Query("code")
if code == "" {
error := c.Query("error")
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("OAuth callback is missing 'code'. OAuth provider responded with error: %s", error))
}
verifier := c.Cookies("jade-edgedb-pkce-verifier")
if verifier == "" {
return c.Status(fiber.StatusBadRequest).SendString("Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?")
}
fmt.Println("handleCallback code: " + code)
fmt.Println("handleCallback verifier: " + verifier)
codeExchangeURL := fmt.Sprintf("%s/token?code=%s&verifier=%s", EDGEDB_AUTH_BASE_URL, code, verifier)
resp, err := http.Get(codeExchangeURL)
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error from the auth server: %s", err.Error()))
}
defer resp.Body.Close()
if resp.StatusCode != fiber.StatusOK {
body, _ := io.ReadAll(resp.Body)
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error from the auth server: %s", string(body)))
}
var tokenResponse struct {
AuthToken string `json:"auth_token"`
}
err = json.NewDecoder(resp.Body).Decode(&tokenResponse)
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error decoding auth server response: %s", err.Error()))
}
c.Cookie(&fiber.Cookie{
Name: "edgedb-auth-token",
Value: tokenResponse.AuthToken,
HTTPOnly: true,
Path: "/",
Secure: true,
SameSite: "Strict",
})
fmt.Println("Login successful")
fmt.Println("edgedb-auth-token: " + tokenResponse.AuthToken)
return c.SendStatus(fiber.StatusNoContent)
}

View File

@ -12,13 +12,11 @@ import (
var userTmpl *pongo2.Template var userTmpl *pongo2.Template
var botTmpl *pongo2.Template var botTmpl *pongo2.Template
var modelsPopoverTmpl *pongo2.Template var modelsPopoverTmpl *pongo2.Template
var conversationsPopoverTmpl *pongo2.Template
func main() { func main() {
botTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-bot.html")) botTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-bot.html"))
userTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-user.html")) userTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-user.html"))
modelsPopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/modelsPopover.html")) modelsPopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/modelsPopover.html"))
conversationsPopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/conversationsPopover.html"))
// Import HTML using django engine/template // Import HTML using django engine/template
engine := django.New("./views", ".html") engine := django.New("./views", ".html")
@ -45,6 +43,11 @@ func main() {
app.Get("/generateMultipleMessages", GenerateMultipleMessages) // Generate multiple messages app.Get("/generateMultipleMessages", GenerateMultipleMessages) // Generate multiple messages
app.Get("/messageContent", GetMessageContentHandler) app.Get("/messageContent", GetMessageContentHandler)
app.Get("/auth/ui/signin", handleUiSignIn)
app.Get("/auth/ui/signup", handleUiSignUp)
app.Get("/auth/callback", handleCallback)
app.Get("/auth/callbackSignup", handleCallbackSignup)
app.Get("test", func(c *fiber.Ctx) error { app.Get("test", func(c *fiber.Ctx) error {
fmt.Println("test") fmt.Println("test")
return c.SendString("") return c.SendString("")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,4 +1,5 @@
<div class="chat-container mt-5"> <div class="chat-container mt-5">
<hx hx-get="/loadChat" hx-trigger="load" hx-swap="outerHTML"></hx> <hx hx-get="/loadChat" hx-trigger="load" hx-swap="outerHTML"></hx>

View File

@ -53,6 +53,8 @@
gap: 10px; gap: 10px;
} }
</style> </style>
<meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">
</head> </head>
<body> <body>

View File

@ -1,32 +0,0 @@
<div class="chat-container mt-5">
<hx hx-get="/loadChat" hx-trigger="load" hx-swap="outerHTML"></hx>
<div class="chat-input-container">
<form class="control" hx-post="/requestMultipleMessages" hx-swap="beforeend settle:200ms"
hx-target="#chat-messages" id="chat-input-form" class="chat-input"
hx-include="[name='message'], [name^='model-check-']">
<div class="textarea-wrapper">
<textarea class="textarea" placeholder="Type your message here..." name="message"></textarea>
<div class="button-group">
<hx hx-get="/loadModelSelection" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<div class="dropdown is-hoverable is-up">
<div class="dropdown-trigger">
<button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu4">
<span>Settings</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu4" role="menu">
<div class="dropdown-content">
<div class="dropdown-item">
<h5>Hello</h5>
</div>
</div>
</div>
</div>
<button type="submit" class="send-button button is-primary is-small">Send</button>
</div>
</div>
</form>
</div>
</div>

View File

@ -8,7 +8,7 @@
hx-target="#content-{{ ConversationAreaId }}" onclick="toggleGrayscale(this)"> hx-target="#content-{{ ConversationAreaId }}" onclick="toggleGrayscale(this)">
<figure class="image is-48x48" style="flex-shrink: 0;"> <figure class="image is-48x48" style="flex-shrink: 0;">
<img src="icons/{{ message.Icon }}.png" alt="User Image" {% if message.Hidden %} <img src="icons/{{ message.Icon }}.png" alt="User Image" {% if message.Hidden %}
style="filter: grayscale(100%);" {% endif %}> style="filter: grayscale(100%);" {% endif %} title="{{ message.Name }}">
</figure> </figure>
</a> </a>
</div> </div>

View File

@ -10,7 +10,16 @@
</div> </div>
<div class="navbar-menu"> <div class="navbar-menu">
<div class="navbar-end"> <div class="navbar-end">
<img id="spinner" class="htmx-indicator" src="/puff.svg" /> <div class="navbar-item">
<div class="buttons">
<a class="button is-light is-small" href="/auth/ui/signin">
Log in
</a>
<a class="button is-primary" href="/auth/ui/signup">
<strong>Sign up</strong>
</a>
</div>
</div>
</div> </div>
</div> </div>
</nav> </nav>