diff options
Diffstat (limited to '')
-rw-r--r-- | db.go | 16 | ||||
-rw-r--r-- | fbfp.scfg.example | 10 | ||||
-rw-r--r-- | go.mod | 8 | ||||
-rw-r--r-- | go.sum | 28 | ||||
-rw-r--r-- | index.go | 99 | ||||
-rw-r--r-- | main.go | 42 | ||||
-rw-r--r-- | oidc.go | 55 | ||||
-rw-r--r-- | schema.sql | 4 |
8 files changed, 198 insertions, 64 deletions
@@ -1,19 +1,19 @@ package main import ( - "database/sql" + "context" + "errors" - _ "github.com/mattn/go-sqlite3" + "github.com/jackc/pgx/v5/pgxpool" ) -var db *sql.DB +var db *pgxpool.Pool func setup_database() error { var err error - db, err = sql.Open(config.Db.Type, config.Db.Conn) - if err != nil { - return err - } else { - return nil + if config.Db.Type != "postgres" { + return errors.New("At the moment, the only supported database type is postgres") } + db, err = pgxpool.New(context.Background(), config.Db.Conn) + return err } diff --git a/fbfp.scfg.example b/fbfp.scfg.example index e21cca1..f00cfba 100644 --- a/fbfp.scfg.example +++ b/fbfp.scfg.example @@ -35,13 +35,13 @@ listen { } db { - # What type of database should we use? Currently, only "sqlite" is + # What type of database should we use? Currently, only "postgres" is # supported. - type sqlite3 + type postgres - # What is the connection string to database? For SQLite, this is - # simply a path to the database file. - conn test.db + # What is the connection string to database? + # Example: postgresql:///fbfp?host=/var/run/postgresql + conn postgresql:///fbfp?host=/var/run/postgresql } openid { @@ -7,10 +7,16 @@ require git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082 require ( github.com/MicahParks/keyfunc/v3 v3.3.3 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/mattn/go-sqlite3 v1.14.22 + github.com/jackc/pgx/v5 v5.6.0 ) require ( github.com/MicahParks/jwkset v0.5.18 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.6.0 // indirect ) @@ -4,11 +4,35 @@ github.com/MicahParks/jwkset v0.5.18 h1:WLdyMngF7rCrnstQxA7mpRoxeaWqGzPM/0z40PJU github.com/MicahParks/jwkset v0.5.18/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY= github.com/MicahParks/keyfunc/v3 v3.3.3 h1:c6j9oSu1YUo0k//KwF1miIQlEMtqNlj7XBFLB8jtEmY= github.com/MicahParks/keyfunc/v3 v3.3.3/go.mod h1:f/UMyXdKfkZzmBeBFUeYk+zu066J1Fcl48f7Wnl5Z48= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/index.go b/index.go new file mode 100644 index 0000000..9b53b6c --- /dev/null +++ b/index.go @@ -0,0 +1,99 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "net/http" + + "github.com/jackc/pgx/v5" +) + +func handle_index(w http.ResponseWriter, req *http.Request) { + session_cookie, err := req.Cookie("session") + if errors.Is(err, http.ErrNoCookie) { + err = tmpl.ExecuteTemplate( + w, + "index_login", + map[string]string{ + "authUrl": generate_authorization_url(), + }, + ) + if err != nil { + log.Println(err) + return + } + return + } else if err != nil { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(400) + w.Write([]byte(fmt.Sprintf( + "Error\n" + + "Unable to check cookie.", + ))) + return + } + var userid string + var expr int + err = db.QueryRow(context.Background(), "SELECT userid, expr FROM sessions WHERE cookie = $1", session_cookie.Value).Scan(&userid, &expr) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + err = tmpl.ExecuteTemplate( + w, + "index_login", + map[string]interface{}{ + "authUrl": generate_authorization_url(), + "notes": []string{"Technically you have a session cookie, but it seems invalid."}, + }, + ) + if err != nil { + log.Println(err) + return + } + return + } else { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(500) + w.Write([]byte(fmt.Sprintf( + "Error\nUnexpected database error.\n%s\n", + err, + ))) + return + } + } + var name string + err = db.QueryRow(context.Background(), "SELECT name FROM users WHERE id = $1", userid).Scan(&name) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(500) + w.Write([]byte(fmt.Sprintf( + "Error\nYour user doesn't exist. (This looks like a data integrity error.)\n%s\n", + err, + ))) + return + } else { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(500) + w.Write([]byte(fmt.Sprintf( + "Error\nUnexpected database error.\n%s\n", + err, + ))) + return + } + } + err = tmpl.ExecuteTemplate( + w, + "index", + map[string]interface{}{ + "user": map[string]interface{}{ + "Name": name, + }, + }, + ) + if err != nil { + log.Println(err) + return + } +} @@ -21,8 +21,6 @@ package main import ( - "errors" - "fmt" "html/template" "log" "net" @@ -32,46 +30,6 @@ import ( var tmpl *template.Template -func handle_index(w http.ResponseWriter, req *http.Request) { - session_cookie, err := req.Cookie("session") - if errors.Is(err, http.ErrNoCookie) { - err = tmpl.ExecuteTemplate( - w, - "index_login", - map[string]string{ - "authUrl": generate_authorization_url(), - }, - ) - if err != nil { - log.Println(err) - return - } - return - } else if err != nil { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(400) - w.Write([]byte(fmt.Sprintf( - "Error\n" + - "Unable to check cookie.", - ))) - return - } - err = tmpl.ExecuteTemplate( - w, - "index", - map[string]interface{}{ - "user": map[string]interface{}{ - "Name": "NAME", - }, - }, - ) - if err != nil { - log.Println(err) - return - } - _ = session_cookie -} - func main() { /* * This seems necessary because in the following sections I need to @@ -21,6 +21,7 @@ package main import ( + "context" "encoding/json" "errors" "fmt" @@ -30,6 +31,7 @@ import ( "github.com/MicahParks/keyfunc/v3" "github.com/golang-jwt/jwt/v5" + "github.com/jackc/pgx/v5/pgconn" ) var openid_configuration struct { @@ -246,15 +248,60 @@ func handle_oidc(w http.ResponseWriter, req *http.Request) { http.SetCookie(w, &cookie) w.Header().Set("Content-Type", "text/plain; charset=utf-8") - result, err := db.Exec( - "INSERT INTO users (id, name, email) VALUES (?, ?, ?)", + _, err = db.Exec( + context.Background(), + "INSERT INTO users (id, name, email) VALUES ($1, $2, $3)", claims.Subject, claims.Name, claims.Email, ) - // TODO: handle err + if err != nil { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + if pgErr.Code == "23505" { + _, err := db.Exec( + context.Background(), + "UPDATE users SET (name, email) = ($1, $2) WHERE id = $3", + claims.Name, + claims.Email, + claims.Subject, + ) + if err != nil { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(500) + w.Write([]byte(fmt.Sprintf("Error\nDatabase error while updating your account.\n%s\n", err))) + return + } + } + } else { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(500) + w.Write([]byte(fmt.Sprintf("Error\nDatabase error while attempting to insert account info.\n%s\n", err))) + return + } + } - fmt.Println(result, err) + _, err = db.Exec( + context.Background(), + "INSERT INTO sessions(userid, cookie, expr) VALUES ($1, $2, $3)", + claims.Subject, + cookie_value, + 1881839332, /* TODO */ + ) + if err != nil { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) && pgErr.Code == "23505" { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(500) + w.Write([]byte(fmt.Sprintf("Error\nCookie collision! Could you try signing in again?\n%s\n", err))) + return + } else { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(500) + w.Write([]byte(fmt.Sprintf("Error\nDatabase error while attempting to insert session info.\n%s\n", err))) + return + } + } http.Redirect(w, req, "/", 303) @@ -5,7 +5,7 @@ CREATE TABLE users ( ); CREATE TABLE sessions ( userid TEXT NOT NULL, - cookie TEXT, - expr INTEGER, + cookie TEXT PRIMARY KEY NOT NULL, + expr INTEGER NOT NULL, FOREIGN KEY(userid) REFERENCES users(id) ); |