From ae39f02812bcfe903e956220c890bfb7b9bb9ff4 Mon Sep 17 00:00:00 2001 From: Emile Date: Wed, 19 Feb 2025 19:53:25 +0100 Subject: removed the backend, added the frontend with oidc support So I've added oidc support which is nice, yet I have to test this with some https foo, so I'm pushing this. --- nix/templates/goapp/backend/db.go | 37 --- nix/templates/goapp/backend/default.nix | 16 -- nix/templates/goapp/backend/go.mod | 25 -- nix/templates/goapp/backend/go.sum | 59 ----- nix/templates/goapp/backend/log.go | 34 --- nix/templates/goapp/backend/main.go | 80 ------ nix/templates/goapp/backend/sqlitestore.go | 284 -------------------- nix/templates/goapp/flake.nix | 1 - nix/templates/goapp/frontend/db.go | 37 --- nix/templates/goapp/frontend/default.nix | 4 +- nix/templates/goapp/frontend/go.mod | 13 +- nix/templates/goapp/frontend/go.sum | 26 +- nix/templates/goapp/frontend/log.go | 34 --- nix/templates/goapp/frontend/main.db | Bin 0 -> 8192 bytes nix/templates/goapp/frontend/main.go | 80 ------ nix/templates/goapp/frontend/run.sh | 9 + nix/templates/goapp/frontend/server.log | 179 +++++++++++++ nix/templates/goapp/frontend/sessions.db | Bin 0 -> 24576 bytes nix/templates/goapp/frontend/sqlitestore.go | 284 -------------------- nix/templates/goapp/frontend/src/db.go | 37 +++ nix/templates/goapp/frontend/src/handlers.go | 236 +++++++++++++++++ nix/templates/goapp/frontend/src/init.go | 76 ++++++ nix/templates/goapp/frontend/src/log.go | 34 +++ nix/templates/goapp/frontend/src/main.go | 96 +++++++ nix/templates/goapp/frontend/src/sqlitestore.go | 285 +++++++++++++++++++++ nix/templates/goapp/frontend/src/templates.go | 42 +++ nix/templates/goapp/frontend/src/types.go | 65 +++++ nix/templates/goapp/frontend/src/util.go | 58 +++++ nix/templates/goapp/frontend/templates/footer.html | 8 + nix/templates/goapp/frontend/templates/head.html | 153 +++++++++++ nix/templates/goapp/frontend/templates/index.html | 81 ++++++ nix/templates/goapp/frontend/templates/login.html | 41 +++ nix/templates/goapp/frontend/templates/nav.html | 41 +++ 33 files changed, 1475 insertions(+), 980 deletions(-) delete mode 100644 nix/templates/goapp/backend/db.go delete mode 100644 nix/templates/goapp/backend/default.nix delete mode 100644 nix/templates/goapp/backend/go.mod delete mode 100644 nix/templates/goapp/backend/go.sum delete mode 100644 nix/templates/goapp/backend/log.go delete mode 100644 nix/templates/goapp/backend/main.go delete mode 100644 nix/templates/goapp/backend/sqlitestore.go delete mode 100644 nix/templates/goapp/frontend/db.go delete mode 100644 nix/templates/goapp/frontend/log.go create mode 100644 nix/templates/goapp/frontend/main.db delete mode 100644 nix/templates/goapp/frontend/main.go create mode 100755 nix/templates/goapp/frontend/run.sh create mode 100644 nix/templates/goapp/frontend/server.log create mode 100644 nix/templates/goapp/frontend/sessions.db delete mode 100644 nix/templates/goapp/frontend/sqlitestore.go create mode 100644 nix/templates/goapp/frontend/src/db.go create mode 100644 nix/templates/goapp/frontend/src/handlers.go create mode 100644 nix/templates/goapp/frontend/src/init.go create mode 100644 nix/templates/goapp/frontend/src/log.go create mode 100644 nix/templates/goapp/frontend/src/main.go create mode 100644 nix/templates/goapp/frontend/src/sqlitestore.go create mode 100644 nix/templates/goapp/frontend/src/templates.go create mode 100644 nix/templates/goapp/frontend/src/types.go create mode 100644 nix/templates/goapp/frontend/src/util.go create mode 100644 nix/templates/goapp/frontend/templates/footer.html create mode 100644 nix/templates/goapp/frontend/templates/head.html create mode 100644 nix/templates/goapp/frontend/templates/index.html create mode 100644 nix/templates/goapp/frontend/templates/login.html create mode 100644 nix/templates/goapp/frontend/templates/nav.html (limited to 'nix/templates') diff --git a/nix/templates/goapp/backend/db.go b/nix/templates/goapp/backend/db.go deleted file mode 100644 index fd3605a..0000000 --- a/nix/templates/goapp/backend/db.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "database/sql" - "log" - - _ "github.com/mattn/go-sqlite3" -) - -const create string = ` -CREATE TABLE IF NOT EXISTS users ( - id INTEGER NOT NULL PRIMARY KEY, - created_at DATETIME NOT NULL, - name TEXT, - passwordHash TEXT -); -` - -type State struct { - db *sql.DB // the database storing the "business data" - sessions *SqliteStore // the database storing sessions -} - -func NewState() (*State, error) { - db, err := sql.Open("sqlite3", databasePath) - if err != nil { - log.Println("Error opening the db: ", err) - return nil, err - } - if _, err := db.Exec(create); err != nil { - log.Println("Error creating the tables: ", err) - return nil, err - } - return &State{ - db: db, - }, nil -} diff --git a/nix/templates/goapp/backend/default.nix b/nix/templates/goapp/backend/default.nix deleted file mode 100644 index f4ed3a4..0000000 --- a/nix/templates/goapp/backend/default.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ pkgs, name, ... }: - -let - version = "0.0.1"; -in -pkgs.buildGoModule { - name = "${name}-${version}"; - pname = "${name}"; - version = "${version}"; - - src = ./.; - subPackages = [ "" ]; - vendorHash = "sha256-tIk8lmyuVETrOW7fA7K7uNNXAAtJAYSM4uH+xZaMWqc="; - - doCheck = true; -} diff --git a/nix/templates/goapp/backend/go.mod b/nix/templates/goapp/backend/go.mod deleted file mode 100644 index 40a7d7f..0000000 --- a/nix/templates/goapp/backend/go.mod +++ /dev/null @@ -1,25 +0,0 @@ -module github.com/hanemile/goapp/backend - -go 1.23.5 - -require ( - github.com/gorilla/handlers v1.5.2 - github.com/gorilla/mux v1.8.1 - github.com/gorilla/securecookie v1.1.2 - github.com/gorilla/sessions v1.4.0 - modernc.org/sqlite v1.34.5 -) - -require ( - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.24 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - golang.org/x/sys v0.22.0 // indirect - modernc.org/libc v1.55.3 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.8.0 // indirect -) diff --git a/nix/templates/goapp/backend/go.sum b/nix/templates/goapp/backend/go.sum deleted file mode 100644 index 53d3f31..0000000 --- a/nix/templates/goapp/backend/go.sum +++ /dev/null @@ -1,59 +0,0 @@ -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= -github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= -github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= -modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= -modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= -modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= -modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= -modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/nix/templates/goapp/backend/log.go b/nix/templates/goapp/backend/log.go deleted file mode 100644 index 5af719a..0000000 --- a/nix/templates/goapp/backend/log.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "net/http" - "os" - - "github.com/gorilla/handlers" -) - -// Defines a middleware containing a logfile -// -// This is done to combine gorilla/handlers with gorilla/mux middlewares to -// just use r.Use(logger.Middleware) once instead of adding this to all -// handlers manually (Yes, I'm really missing macros in Go...) -type loggingMiddleware struct { - logFile *os.File -} - -func (l *loggingMiddleware) Middleware(next http.Handler) http.Handler { - return handlers.LoggingHandler(l.logFile, next) -} - -func authMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - session, _ := globalState.sessions.Get(r, "session") - username := session.Values["username"] - - if username == nil { - http.Redirect(w, r, "/login", http.StatusSeeOther) - } else { - next.ServeHTTP(w, r) - } - }) -} diff --git a/nix/templates/goapp/backend/main.go b/nix/templates/goapp/backend/main.go deleted file mode 100644 index d793869..0000000 --- a/nix/templates/goapp/backend/main.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "os" - "time" - - "github.com/gorilla/mux" -) - -var ( - host string - port int - logFilePath string - databasePath string - sessiondbPath string - globalState *State -) - -func initFlags() { - flag.StringVar(&host, "host", "127.0.0.1", "The host to listen on") - flag.StringVar(&host, "h", "127.0.0.1", "The host to listen on (shorthand)") - - flag.IntVar(&port, "port", 8080, "The port to listen on") - flag.IntVar(&port, "p", 8080, "The port to listen on (shorthand)") - - flag.StringVar(&logFilePath, "logfilepath", "./server.log", "The path to the log file") - flag.StringVar(&databasePath, "databasepath", "./main.db", "The path to the main database") - flag.StringVar(&sessiondbPath, "sessiondbpath", "./sessions.db", "The path to the session database") -} - -func indexHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello World from the backend") -} - -func main() { - initFlags() - flag.Parse() - - // log init - log.Println("[i] Setting up logging...") - logFile, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664) - if err != nil { - log.Fatal("Error opening the server.log file: ", err) - } - logger := loggingMiddleware{logFile} - - // db init - log.Println("[i] Setting up Global State Struct...") - s, err := NewState() - if err != nil { - log.Fatal("Error creating the NewState(): ", err) - } - globalState = s - - // session init - log.Println("[i] Setting up Session Storage...") - store, err := NewSqliteStore(sessiondbPath, "sessions", "/", 3600, []byte(os.Getenv("SESSION_KEY"))) - if err != nil { - panic(err) - } - globalState.sessions = store - - r := mux.NewRouter() - r.Use(logger.Middleware) - r.HandleFunc("/", indexHandler) - - srv := &http.Server{ - Handler: r, - Addr: ":8080", - WriteTimeout: 15 * time.Second, - ReadTimeout: 15 * time.Second, - } - - log.Printf("[i] Running the server on %s", srv.Addr) - log.Fatal(srv.ListenAndServe()) -} diff --git a/nix/templates/goapp/backend/sqlitestore.go b/nix/templates/goapp/backend/sqlitestore.go deleted file mode 100644 index 6f59d15..0000000 --- a/nix/templates/goapp/backend/sqlitestore.go +++ /dev/null @@ -1,284 +0,0 @@ -/* - Gorilla Sessions backend for SQLite. - -Copyright (c) 2013 Contributors. See the list of contributors in the CONTRIBUTORS file for details. - -This software is licensed under a MIT style license available in the LICENSE file. -*/ -package main - -import ( - "database/sql" - "encoding/gob" - "errors" - "fmt" - "log" - "net/http" - "strings" - "time" - - "github.com/gorilla/securecookie" - "github.com/gorilla/sessions" - _ "modernc.org/sqlite" -) - -type SqliteStore struct { - db DB - stmtInsert *sql.Stmt - stmtDelete *sql.Stmt - stmtUpdate *sql.Stmt - stmtSelect *sql.Stmt - - Codecs []securecookie.Codec - Options *sessions.Options - table string -} - -type sessionRow struct { - id string - data string - createdOn time.Time - modifiedOn time.Time - expiresOn time.Time -} - -type DB interface { - Exec(query string, args ...interface{}) (sql.Result, error) - Prepare(query string) (*sql.Stmt, error) - Close() error -} - -func init() { - gob.Register(time.Time{}) -} - -func NewSqliteStore(endpoint string, tableName string, path string, maxAge int, keyPairs ...[]byte) (*SqliteStore, error) { - db, err := sql.Open("sqlite3", endpoint) - if err != nil { - return nil, err - } - - return NewSqliteStoreFromConnection(db, tableName, path, maxAge, keyPairs...) -} - -func NewSqliteStoreFromConnection(db DB, tableName string, path string, maxAge int, keyPairs ...[]byte) (*SqliteStore, error) { - // Make sure table name is enclosed. - tableName = "`" + strings.Trim(tableName, "`") + "`" - - cTableQ := "CREATE TABLE IF NOT EXISTS " + - tableName + " (id INTEGER PRIMARY KEY, " + - "session_data LONGBLOB, " + - "created_on TIMESTAMP DEFAULT 0, " + - "modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " + - "expires_on TIMESTAMP DEFAULT 0);" - if _, err := db.Exec(cTableQ); err != nil { - return nil, err - } - - insQ := "INSERT INTO " + tableName + - "(id, session_data, created_on, modified_on, expires_on) VALUES (NULL, ?, ?, ?, ?)" - stmtInsert, stmtErr := db.Prepare(insQ) - if stmtErr != nil { - return nil, stmtErr - } - - delQ := "DELETE FROM " + tableName + " WHERE id = ?" - stmtDelete, stmtErr := db.Prepare(delQ) - if stmtErr != nil { - return nil, stmtErr - } - - updQ := "UPDATE " + tableName + " SET session_data = ?, created_on = ?, expires_on = ? " + - "WHERE id = ?" - stmtUpdate, stmtErr := db.Prepare(updQ) - if stmtErr != nil { - return nil, stmtErr - } - - selQ := "SELECT id, session_data, created_on, modified_on, expires_on from " + - tableName + " WHERE id = ?" - stmtSelect, stmtErr := db.Prepare(selQ) - if stmtErr != nil { - return nil, stmtErr - } - - return &SqliteStore{ - db: db, - stmtInsert: stmtInsert, - stmtDelete: stmtDelete, - stmtUpdate: stmtUpdate, - stmtSelect: stmtSelect, - Codecs: securecookie.CodecsFromPairs(keyPairs...), - Options: &sessions.Options{ - Path: path, - MaxAge: maxAge, - }, - table: tableName, - }, nil -} - -func (m *SqliteStore) Close() { - m.stmtSelect.Close() - m.stmtUpdate.Close() - m.stmtDelete.Close() - m.stmtInsert.Close() - m.db.Close() -} - -func (m *SqliteStore) Get(r *http.Request, name string) (*sessions.Session, error) { - return sessions.GetRegistry(r).Get(m, name) -} - -func (m *SqliteStore) New(r *http.Request, name string) (*sessions.Session, error) { - session := sessions.NewSession(m, name) - session.Options = &sessions.Options{ - Path: m.Options.Path, - MaxAge: m.Options.MaxAge, - } - session.IsNew = true - var err error - if cook, errCookie := r.Cookie(name); errCookie == nil { - err = securecookie.DecodeMulti(name, cook.Value, &session.ID, m.Codecs...) - if err == nil { - err = m.load(session) - if err == nil { - session.IsNew = false - } else { - err = nil - } - } - } - return session, err -} - -func (m *SqliteStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { - var err error - if session.ID == "" { - if err = m.insert(session); err != nil { - return err - } - } else if err = m.save(session); err != nil { - return err - } - encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, m.Codecs...) - if err != nil { - return err - } - http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options)) - return nil -} - -func (m *SqliteStore) insert(session *sessions.Session) error { - var createdOn time.Time - var modifiedOn time.Time - var expiresOn time.Time - crOn := session.Values["created_on"] - if crOn == nil { - createdOn = time.Now() - } else { - createdOn = crOn.(time.Time) - } - modifiedOn = createdOn - exOn := session.Values["expires_on"] - if exOn == nil { - expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) - } else { - expiresOn = exOn.(time.Time) - } - delete(session.Values, "created_on") - delete(session.Values, "expires_on") - delete(session.Values, "modified_on") - - encoded, encErr := securecookie.EncodeMulti(session.Name(), session.Values, m.Codecs...) - if encErr != nil { - return encErr - } - res, insErr := m.stmtInsert.Exec(encoded, createdOn, modifiedOn, expiresOn) - if insErr != nil { - return insErr - } - lastInserted, lInsErr := res.LastInsertId() - if lInsErr != nil { - return lInsErr - } - session.ID = fmt.Sprintf("%d", lastInserted) - return nil -} - -func (m *SqliteStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { - - // Set cookie to expire. - options := *session.Options - options.MaxAge = -1 - http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options)) - // Clear session values. - for k := range session.Values { - delete(session.Values, k) - } - - _, delErr := m.stmtDelete.Exec(session.ID) - if delErr != nil { - return delErr - } - return nil -} - -func (m *SqliteStore) save(session *sessions.Session) error { - if session.IsNew == true { - return m.insert(session) - } - var createdOn time.Time - var expiresOn time.Time - crOn := session.Values["created_on"] - if crOn == nil { - createdOn = time.Now() - } else { - createdOn = crOn.(time.Time) - } - - exOn := session.Values["expires_on"] - if exOn == nil { - expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) - log.Print("nil") - } else { - expiresOn = exOn.(time.Time) - if expiresOn.Sub(time.Now().Add(time.Second*time.Duration(session.Options.MaxAge))) < 0 { - expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) - } - } - - delete(session.Values, "created_on") - delete(session.Values, "expires_on") - delete(session.Values, "modified_on") - encoded, encErr := securecookie.EncodeMulti(session.Name(), session.Values, m.Codecs...) - if encErr != nil { - return encErr - } - _, updErr := m.stmtUpdate.Exec(encoded, createdOn, expiresOn, session.ID) - if updErr != nil { - return updErr - } - return nil -} - -func (m *SqliteStore) load(session *sessions.Session) error { - row := m.stmtSelect.QueryRow(session.ID) - sess := sessionRow{} - scanErr := row.Scan(&sess.id, &sess.data, &sess.createdOn, &sess.modifiedOn, &sess.expiresOn) - if scanErr != nil { - return scanErr - } - if sess.expiresOn.Sub(time.Now()) < 0 { - log.Printf("Session expired on %s, but it is %s now.", sess.expiresOn, time.Now()) - return errors.New("Session expired") - } - err := securecookie.DecodeMulti(session.Name(), sess.data, &session.Values, m.Codecs...) - if err != nil { - return err - } - session.Values["created_on"] = sess.createdOn - session.Values["modified_on"] = sess.modifiedOn - session.Values["expires_on"] = sess.expiresOn - return nil - -} diff --git a/nix/templates/goapp/flake.nix b/nix/templates/goapp/flake.nix index 8303eb3..1ca876f 100644 --- a/nix/templates/goapp/flake.nix +++ b/nix/templates/goapp/flake.nix @@ -30,7 +30,6 @@ in { packages = { } - // (package-and-docker "backend") // (package-and-docker "frontend"); devShells.default = pkgs.mkShell { diff --git a/nix/templates/goapp/frontend/db.go b/nix/templates/goapp/frontend/db.go deleted file mode 100644 index fd3605a..0000000 --- a/nix/templates/goapp/frontend/db.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "database/sql" - "log" - - _ "github.com/mattn/go-sqlite3" -) - -const create string = ` -CREATE TABLE IF NOT EXISTS users ( - id INTEGER NOT NULL PRIMARY KEY, - created_at DATETIME NOT NULL, - name TEXT, - passwordHash TEXT -); -` - -type State struct { - db *sql.DB // the database storing the "business data" - sessions *SqliteStore // the database storing sessions -} - -func NewState() (*State, error) { - db, err := sql.Open("sqlite3", databasePath) - if err != nil { - log.Println("Error opening the db: ", err) - return nil, err - } - if _, err := db.Exec(create); err != nil { - log.Println("Error creating the tables: ", err) - return nil, err - } - return &State{ - db: db, - }, nil -} diff --git a/nix/templates/goapp/frontend/default.nix b/nix/templates/goapp/frontend/default.nix index f4ed3a4..42ccb79 100644 --- a/nix/templates/goapp/frontend/default.nix +++ b/nix/templates/goapp/frontend/default.nix @@ -9,8 +9,8 @@ pkgs.buildGoModule { version = "${version}"; src = ./.; - subPackages = [ "" ]; - vendorHash = "sha256-tIk8lmyuVETrOW7fA7K7uNNXAAtJAYSM4uH+xZaMWqc="; + subPackages = [ "src" ]; + vendorHash = "sha256-VXuhsXejduIcthawj4qu7hruBEDegj27YY0ym5srMQY="; doCheck = true; } diff --git a/nix/templates/goapp/frontend/go.mod b/nix/templates/goapp/frontend/go.mod index 1532a66..fecf4ac 100644 --- a/nix/templates/goapp/frontend/go.mod +++ b/nix/templates/goapp/frontend/go.mod @@ -1,4 +1,4 @@ -module github.com/hanemile/goapp/frontend +module github.com/hanemile/goapp/backend go 1.23.5 @@ -7,18 +7,25 @@ require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/securecookie v1.1.2 github.com/gorilla/sessions v1.4.0 + github.com/mattn/go-sqlite3 v1.14.24 + golang.org/x/crypto v0.33.0 + golang.org/x/oauth2 v0.21.0 modernc.org/sqlite v1.34.5 ) require ( + github.com/coreos/go-oidc/v3 v3.12.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - golang.org/x/sys v0.22.0 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + golang.org/x/sys v0.30.0 // indirect modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect diff --git a/nix/templates/goapp/frontend/go.sum b/nix/templates/goapp/frontend/go.sum index 53d3f31..365e2c5 100644 --- a/nix/templates/goapp/frontend/go.sum +++ b/nix/templates/goapp/frontend/go.sum @@ -1,9 +1,14 @@ +github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= +github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= @@ -18,6 +23,8 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= @@ -26,13 +33,24 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= diff --git a/nix/templates/goapp/frontend/log.go b/nix/templates/goapp/frontend/log.go deleted file mode 100644 index 5af719a..0000000 --- a/nix/templates/goapp/frontend/log.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "net/http" - "os" - - "github.com/gorilla/handlers" -) - -// Defines a middleware containing a logfile -// -// This is done to combine gorilla/handlers with gorilla/mux middlewares to -// just use r.Use(logger.Middleware) once instead of adding this to all -// handlers manually (Yes, I'm really missing macros in Go...) -type loggingMiddleware struct { - logFile *os.File -} - -func (l *loggingMiddleware) Middleware(next http.Handler) http.Handler { - return handlers.LoggingHandler(l.logFile, next) -} - -func authMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - session, _ := globalState.sessions.Get(r, "session") - username := session.Values["username"] - - if username == nil { - http.Redirect(w, r, "/login", http.StatusSeeOther) - } else { - next.ServeHTTP(w, r) - } - }) -} diff --git a/nix/templates/goapp/frontend/main.db b/nix/templates/goapp/frontend/main.db new file mode 100644 index 0000000..da9d88e Binary files /dev/null and b/nix/templates/goapp/frontend/main.db differ diff --git a/nix/templates/goapp/frontend/main.go b/nix/templates/goapp/frontend/main.go deleted file mode 100644 index f6605be..0000000 --- a/nix/templates/goapp/frontend/main.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "os" - "time" - - "github.com/gorilla/mux" -) - -var ( - host string - port int - logFilePath string - databasePath string - sessiondbPath string - globalState *State -) - -func initFlags() { - flag.StringVar(&host, "host", "127.0.0.1", "The host to listen on") - flag.StringVar(&host, "h", "127.0.0.1", "The host to listen on (shorthand)") - - flag.IntVar(&port, "port", 8080, "The port to listen on") - flag.IntVar(&port, "p", 8080, "The port to listen on (shorthand)") - - flag.StringVar(&logFilePath, "logfilepath", "./server.log", "The path to the log file") - flag.StringVar(&databasePath, "databasepath", "./main.db", "The path to the main database") - flag.StringVar(&sessiondbPath, "sessiondbpath", "./sessions.db", "The path to the session database") -} - -func indexHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello World from the frontend") -} - -func main() { - initFlags() - flag.Parse() - - // log init - log.Println("[i] Setting up logging...") - logFile, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664) - if err != nil { - log.Fatal("Error opening the server.log file: ", err) - } - logger := loggingMiddleware{logFile} - - // db init - log.Println("[i] Setting up Global State Struct...") - s, err := NewState() - if err != nil { - log.Fatal("Error creating the NewState(): ", err) - } - globalState = s - - // session init - log.Println("[i] Setting up Session Storage...") - store, err := NewSqliteStore(sessiondbPath, "sessions", "/", 3600, []byte(os.Getenv("SESSION_KEY"))) - if err != nil { - panic(err) - } - globalState.sessions = store - - r := mux.NewRouter() - r.Use(logger.Middleware) - r.HandleFunc("/", indexHandler) - - srv := &http.Server{ - Handler: r, - Addr: ":8080", - WriteTimeout: 15 * time.Second, - ReadTimeout: 15 * time.Second, - } - - log.Printf("[i] Running the server on %s", srv.Addr) - log.Fatal(srv.ListenAndServe()) -} diff --git a/nix/templates/goapp/frontend/run.sh b/nix/templates/goapp/frontend/run.sh new file mode 100755 index 0000000..fb3c7b3 --- /dev/null +++ b/nix/templates/goapp/frontend/run.sh @@ -0,0 +1,9 @@ +export CLIENT_ID=goapp +export CLIENT_SECRET=KGFO5LQnUxu1Zs.35gOem3MaG8odthg1U0v0.kScVPS6TPTWVRnAdT_nj4PYYSfuU6jdzTM6 +export CLIENT_CALLBACK_URL=http://localhost:8080/oauth2/callback +export VERSION=0.0.1 +export SESSION_KEY=aes1Itheich4aeQu9Ouz7ahcaiVoogh9 +go run ./... \ + --id goapp \ + --issuer "https://sso.emile.space" \ + --secret "KGFO5LQnUxu1Zs.35gOem3MaG8odthg1U0v0.kScVPS6TPTWVRnAdT_nj4PYYSfuU6jdzTM6" diff --git a/nix/templates/goapp/frontend/server.log b/nix/templates/goapp/frontend/server.log new file mode 100644 index 0000000..4b6cff5 --- /dev/null +++ b/nix/templates/goapp/frontend/server.log @@ -0,0 +1,179 @@ +::1 - - [16/Feb/2025:23:03:46 +0100] "GET / HTTP/1.1" 200 5445 +::1 - - [16/Feb/2025:23:04:16 +0100] "GET / HTTP/1.1" 200 5445 +::1 - - [16/Feb/2025:23:04:17 +0100] "GET / HTTP/1.1" 200 5445 +::1 - - [16/Feb/2025:23:04:58 +0100] "GET / HTTP/1.1" 200 5509 +::1 - - [16/Feb/2025:23:05:03 +0100] "GET / HTTP/1.1" 200 5509 +::1 - - [16/Feb/2025:23:05:23 +0100] "GET / HTTP/1.1" 200 5573 +::1 - - [16/Feb/2025:23:05:33 +0100] "GET / HTTP/1.1" 200 5573 +::1 - - [16/Feb/2025:23:06:07 +0100] "GET / HTTP/1.1" 200 5614 +::1 - - [16/Feb/2025:23:06:17 +0100] "GET / HTTP/1.1" 200 5630 +::1 - - [16/Feb/2025:23:06:37 +0100] "GET / HTTP/1.1" 200 5651 +::1 - - [16/Feb/2025:23:06:59 +0100] "GET / HTTP/1.1" 200 5652 +::1 - - [16/Feb/2025:23:07:28 +0100] "GET / HTTP/1.1" 200 5676 +::1 - - [16/Feb/2025:23:08:26 +0100] "GET / HTTP/1.1" 200 5662 +::1 - - [16/Feb/2025:23:08:30 +0100] "GET / HTTP/1.1" 200 5662 +::1 - - [16/Feb/2025:23:08:42 +0100] "GET / HTTP/1.1" 200 5662 +::1 - - [16/Feb/2025:23:08:43 +0100] "GET / HTTP/1.1" 200 5662 +::1 - - [16/Feb/2025:23:08:54 +0100] "GET / HTTP/1.1" 200 5677 +::1 - - [16/Feb/2025:23:09:25 +0100] "GET / HTTP/1.1" 200 5614 +::1 - - [16/Feb/2025:23:09:44 +0100] "GET / HTTP/1.1" 200 5639 +::1 - - [16/Feb/2025:23:10:13 +0100] "GET / HTTP/1.1" 200 5661 +::1 - - [16/Feb/2025:23:10:48 +0100] "GET / HTTP/1.1" 200 5672 +::1 - - [16/Feb/2025:23:10:49 +0100] "GET / HTTP/1.1" 200 5672 +::1 - - [16/Feb/2025:23:11:32 +0100] "GET / HTTP/1.1" 200 5648 +::1 - - [16/Feb/2025:23:11:32 +0100] "GET / HTTP/1.1" 200 5648 +::1 - - [16/Feb/2025:23:11:57 +0100] "GET / HTTP/1.1" 200 5655 +::1 - - [16/Feb/2025:23:12:11 +0100] "GET / HTTP/1.1" 200 5675 +::1 - - [16/Feb/2025:23:12:13 +0100] "GET / HTTP/1.1" 200 5675 +::1 - - [16/Feb/2025:23:12:46 +0100] "GET / HTTP/1.1" 200 5724 +::1 - - [16/Feb/2025:23:12:54 +0100] "GET / HTTP/1.1" 200 5722 +::1 - - [16/Feb/2025:23:13:03 +0100] "GET / HTTP/1.1" 200 5721 +::1 - - [16/Feb/2025:23:13:11 +0100] "GET / HTTP/1.1" 200 5721 +::1 - - [16/Feb/2025:23:13:42 +0100] "GET / HTTP/1.1" 200 5719 +::1 - - [17/Feb/2025:10:27:07 +0100] "GET / HTTP/1.1" 200 5719 +::1 - - [17/Feb/2025:10:27:09 +0100] "GET / HTTP/1.1" 200 5719 +::1 - - [17/Feb/2025:10:28:32 +0100] "GET / HTTP/1.1" 200 5854 +::1 - - [17/Feb/2025:10:28:40 +0100] "GET / HTTP/1.1" 200 5854 +::1 - - [17/Feb/2025:10:28:46 +0100] "GET / HTTP/1.1" 200 5854 +::1 - - [17/Feb/2025:10:28:52 +0100] "GET / HTTP/1.1" 200 5854 +::1 - - [17/Feb/2025:10:29:22 +0100] "GET / HTTP/1.1" 200 5858 +::1 - - [17/Feb/2025:10:29:54 +0100] "GET / HTTP/1.1" 200 5841 +::1 - - [17/Feb/2025:10:30:09 +0100] "GET / HTTP/1.1" 200 5802 +::1 - - [17/Feb/2025:10:30:10 +0100] "GET / HTTP/1.1" 200 5802 +::1 - - [17/Feb/2025:10:30:20 +0100] "GET / HTTP/1.1" 200 5866 +::1 - - [17/Feb/2025:10:31:14 +0100] "GET / HTTP/1.1" 200 5866 +::1 - - [17/Feb/2025:10:31:26 +0100] "GET / HTTP/1.1" 200 5866 +::1 - - [17/Feb/2025:10:31:31 +0100] "GET / HTTP/1.1" 200 5866 +::1 - - [17/Feb/2025:10:31:47 +0100] "GET / HTTP/1.1" 200 5786 +::1 - - [17/Feb/2025:10:34:25 +0100] "GET / HTTP/1.1" 200 5786 +::1 - - [17/Feb/2025:10:35:22 +0100] "GET / HTTP/1.1" 200 5786 +::1 - - [17/Feb/2025:10:36:10 +0100] "GET / HTTP/1.1" 200 5786 +::1 - - [17/Feb/2025:10:37:10 +0100] "POST /submit HTTP/1.1" 200 10 +::1 - - [17/Feb/2025:10:37:51 +0100] "POST /submit HTTP/1.1" 200 15 +::1 - - [17/Feb/2025:10:37:55 +0100] "GET / HTTP/1.1" 200 5786 +::1 - - [17/Feb/2025:10:37:59 +0100] "POST /submit HTTP/1.1" 200 15 +::1 - - [17/Feb/2025:10:38:26 +0100] "POST /submit HTTP/1.1" 200 35 +::1 - - [17/Feb/2025:10:38:46 +0100] "POST /submit HTTP/1.1" 200 21 +::1 - - [17/Feb/2025:10:40:03 +0100] "GET / HTTP/1.1" 500 33 +::1 - - [17/Feb/2025:10:40:28 +0100] "GET / HTTP/1.1" 500 33 +::1 - - [17/Feb/2025:10:41:20 +0100] "GET / HTTP/1.1" 200 5786 +::1 - - [17/Feb/2025:10:41:38 +0100] "GET / HTTP/1.1" 200 5806 +::1 - - [17/Feb/2025:10:41:44 +0100] "GET / HTTP/1.1" 200 5806 +::1 - - [17/Feb/2025:10:41:53 +0100] "GET / HTTP/1.1" 200 6331 +::1 - - [17/Feb/2025:10:42:24 +0100] "GET / HTTP/1.1" 200 6238 +::1 - - [17/Feb/2025:10:43:00 +0100] "GET / HTTP/1.1" 200 6268 +::1 - - [17/Feb/2025:10:43:10 +0100] "GET / HTTP/1.1" 200 6260 +::1 - - [17/Feb/2025:10:44:54 +0100] "GET / HTTP/1.1" 200 6260 +::1 - - [17/Feb/2025:10:46:05 +0100] "GET / HTTP/1.1" 200 6398 +::1 - - [17/Feb/2025:10:49:07 +0100] "GET / HTTP/1.1" 200 6398 +::1 - - [17/Feb/2025:10:51:15 +0100] "GET / HTTP/1.1" 200 6398 +::1 - - [17/Feb/2025:10:51:16 +0100] "GET / HTTP/1.1" 200 6398 +::1 - - [17/Feb/2025:10:51:26 +0100] "GET / HTTP/1.1" 200 6398 +::1 - - [17/Feb/2025:10:51:27 +0100] "GET / HTTP/1.1" 200 6398 +::1 - - [17/Feb/2025:10:51:45 +0100] "GET /login HTTP/1.1" 200 0 +::1 - - [17/Feb/2025:10:51:59 +0100] "GET /login HTTP/1.1" 200 0 +::1 - - [17/Feb/2025:10:52:56 +0100] "GET /login HTTP/1.1" 200 0 +::1 - - [17/Feb/2025:10:52:58 +0100] "GET /login HTTP/1.1" 200 0 +::1 - - [17/Feb/2025:10:52:59 +0100] "GET /login HTTP/1.1" 200 0 +::1 - - [17/Feb/2025:10:53:11 +0100] "GET /login HTTP/1.1" 200 0 +::1 - - [17/Feb/2025:10:53:13 +0100] "GET /login HTTP/1.1" 200 0 +::1 - - [17/Feb/2025:10:53:29 +0100] "GET /login HTTP/1.1" 200 0 +::1 - - [17/Feb/2025:10:53:30 +0100] "GET /login HTTP/1.1" 200 0 +::1 - - [17/Feb/2025:10:54:04 +0100] "GET /login HTTP/1.1" 200 6974 +::1 - - [17/Feb/2025:10:54:11 +0100] "GET /login HTTP/1.1" 200 6917 +::1 - - [17/Feb/2025:10:54:28 +0100] "GET /login HTTP/1.1" 200 6724 +::1 - - [17/Feb/2025:10:54:28 +0100] "GET /login HTTP/1.1" 200 6724 +::1 - - [17/Feb/2025:10:54:30 +0100] "GET /login HTTP/1.1" 200 6724 +::1 - - [17/Feb/2025:10:54:33 +0100] "GET /login HTTP/1.1" 200 6724 +::1 - - [17/Feb/2025:10:54:35 +0100] "GET /login HTTP/1.1" 200 6724 +::1 - - [17/Feb/2025:10:54:39 +0100] "GET /login HTTP/1.1" 200 6724 +::1 - - [17/Feb/2025:10:54:42 +0100] "GET / HTTP/1.1" 200 6398 +::1 - - [17/Feb/2025:10:54:55 +0100] "GET / HTTP/1.1" 200 6388 +::1 - - [17/Feb/2025:10:54:58 +0100] "POST /submit HTTP/1.1" 200 21 +::1 - - [17/Feb/2025:10:58:19 +0100] "GET / HTTP/1.1" 200 6388 +::1 - - [17/Feb/2025:10:58:42 +0100] "GET / HTTP/1.1" 200 6316 +::1 - - [17/Feb/2025:10:58:43 +0100] "GET / HTTP/1.1" 200 6316 +::1 - - [17/Feb/2025:10:58:43 +0100] "GET / HTTP/1.1" 200 6316 +::1 - - [17/Feb/2025:13:44:10 +0100] "GET / HTTP/1.1" 200 6316 +::1 - - [17/Feb/2025:13:44:11 +0100] "GET /login HTTP/1.1" 200 6724 +::1 - - [17/Feb/2025:13:44:23 +0100] "GET /login HTTP/1.1" 200 6724 +::1 - - [17/Feb/2025:14:37:43 +0100] "GET /login HTTP/1.1" 500 56 +::1 - - [17/Feb/2025:14:38:57 +0100] "GET / HTTP/1.1" 500 33 +::1 - - [17/Feb/2025:14:40:38 +0100] "GET / HTTP/1.1" 200 5683 +::1 - - [17/Feb/2025:14:43:24 +0100] "GET / HTTP/1.1" 200 5683 +::1 - - [17/Feb/2025:14:45:41 +0100] "GET / HTTP/1.1" 200 5683 +::1 - - [17/Feb/2025:14:46:07 +0100] "GET / HTTP/1.1" 200 5683 +::1 - - [17/Feb/2025:14:46:14 +0100] "GET / HTTP/1.1" 200 5894 +::1 - - [17/Feb/2025:14:46:23 +0100] "GET / HTTP/1.1" 200 5734 +::1 - - [17/Feb/2025:14:47:02 +0100] "GET / HTTP/1.1" 200 5835 +::1 - - [17/Feb/2025:14:47:14 +0100] "GET / HTTP/1.1" 200 5873 +::1 - - [17/Feb/2025:14:47:22 +0100] "GET / HTTP/1.1" 200 5899 +::1 - - [17/Feb/2025:14:48:10 +0100] "GET / HTTP/1.1" 200 5790 +::1 - - [17/Feb/2025:14:48:15 +0100] "GET / HTTP/1.1" 200 5899 +::1 - - [17/Feb/2025:14:48:47 +0100] "GET / HTTP/1.1" 200 5899 +::1 - - [17/Feb/2025:14:48:53 +0100] "GET / HTTP/1.1" 200 5790 +::1 - - [17/Feb/2025:14:49:07 +0100] "GET / HTTP/1.1" 200 5927 +::1 - - [17/Feb/2025:14:49:16 +0100] "GET / HTTP/1.1" 200 5790 +::1 - - [17/Feb/2025:14:49:24 +0100] "GET / HTTP/1.1" 200 5919 +::1 - - [17/Feb/2025:14:51:08 +0100] "GET / HTTP/1.1" 500 33 +::1 - - [17/Feb/2025:14:51:48 +0100] "GET / HTTP/1.1" 200 6285 +::1 - - [17/Feb/2025:14:52:24 +0100] "GET / HTTP/1.1" 200 6224 +::1 - - [17/Feb/2025:14:53:46 +0100] "GET / HTTP/1.1" 200 6242 +::1 - - [17/Feb/2025:14:54:34 +0100] "GET / HTTP/1.1" 200 6222 +::1 - - [17/Feb/2025:14:55:14 +0100] "GET / HTTP/1.1" 200 6284 +::1 - - [17/Feb/2025:14:55:18 +0100] "GET / HTTP/1.1" 200 6284 +::1 - - [17/Feb/2025:14:55:45 +0100] "GET / HTTP/1.1" 200 6360 +::1 - - [17/Feb/2025:14:57:06 +0100] "GET / HTTP/1.1" 500 33 +::1 - - [17/Feb/2025:14:57:43 +0100] "GET / HTTP/1.1" 500 33 +::1 - - [17/Feb/2025:14:58:27 +0100] "GET / HTTP/1.1" 500 33 +::1 - - [17/Feb/2025:15:01:47 +0100] "GET / HTTP/1.1" 500 33 +::1 - - [17/Feb/2025:15:02:03 +0100] "GET / HTTP/1.1" 500 33 +::1 - - [17/Feb/2025:15:03:49 +0100] "GET / HTTP/1.1" 200 6360 +::1 - - [17/Feb/2025:15:06:07 +0100] "GET / HTTP/1.1" 200 6412 +::1 - - [17/Feb/2025:15:07:19 +0100] "GET / HTTP/1.1" 200 6412 +::1 - - [17/Feb/2025:15:07:38 +0100] "GET / HTTP/1.1" 200 6412 +::1 - - [17/Feb/2025:15:07:46 +0100] "GET / HTTP/1.1" 200 6514 +::1 - - [17/Feb/2025:15:07:58 +0100] "GET / HTTP/1.1" 200 6491 +::1 - - [17/Feb/2025:15:08:04 +0100] "GET /login HTTP/1.1" 500 56 +::1 - - [17/Feb/2025:15:08:56 +0100] "GET /login HTTP/1.1" 302 87 +::1 - - [17/Feb/2025:15:08:56 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6491 +::1 - - [17/Feb/2025:15:08:58 +0100] "GET /login HTTP/1.1" 302 87 +::1 - - [17/Feb/2025:15:08:58 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6491 +::1 - - [17/Feb/2025:15:20:07 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6586 +::1 - - [17/Feb/2025:15:20:41 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6587 +::1 - - [17/Feb/2025:15:20:45 +0100] "GET /login HTTP/1.1" 302 87 +::1 - - [17/Feb/2025:15:20:45 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6587 +::1 - - [17/Feb/2025:15:20:53 +0100] "GET /login HTTP/1.1" 302 87 +::1 - - [17/Feb/2025:15:20:53 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6587 +::1 - - [17/Feb/2025:15:24:15 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6587 +::1 - - [17/Feb/2025:15:24:16 +0100] "GET /login HTTP/1.1" 302 87 +::1 - - [17/Feb/2025:15:24:16 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:18:57:18 +0100] "GET / HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:18:57:19 +0100] "GET /login HTTP/1.1" 302 87 +::1 - - [19/Feb/2025:18:57:19 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:18:57:44 +0100] "GET /login HTTP/1.1" 302 87 +::1 - - [19/Feb/2025:18:57:44 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:18:58:54 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:18:58:55 +0100] "GET /login HTTP/1.1" 302 87 +::1 - - [19/Feb/2025:18:58:55 +0100] "GET /?client_id=&response_type=code&state=random-string-here HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:19:06:37 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:10:24 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:10:36 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:14:14 +0100] "GET / HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:19:14:16 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:15:01 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:16:08 +0100] "GET / HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:19:16:10 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:18:51 +0100] "GET / HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:19:18:53 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:23:35 +0100] "GET / HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:19:23:37 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:25:20 +0100] "GET /oauth2/callback HTTP/1.1" 500 314 +::1 - - [19/Feb/2025:19:25:37 +0100] "GET /oauth2/callback?code=authelia_ac_lA-8rLxGY4flmo-_DerONxfFPIVk2vpMiaCYZh_6ke0.FBoivMumLtPFauH9sWNVRz51S0FqWjwlFtqKO5sEA88&iss=https%3A%2F%2Fsso.emile.space&scope=openid+profile+email+groups&state=random-string-here HTTP/1.1" 500 314 +::1 - - [19/Feb/2025:19:27:27 +0100] "GET / HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:19:27:28 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:43:04 +0100] "GET / HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:19:43:05 +0100] "GET /login HTTP/1.1" 302 242 +::1 - - [19/Feb/2025:19:43:29 +0100] "GET /oauth2/callback?code=authelia_ac_8UdV__GJCN9gxJrYa629TC3FToyDDhsbacPbJzhvcJ4.uPw2-_N4jQr7xf7JNZ_IZBNHEq-eeOFoZup7Vwjx1Y0&iss=https%3A%2F%2Fsso.emile.space&scope=openid+profile+email+groups&state=random-string-here HTTP/1.1" 500 142 +::1 - - [19/Feb/2025:19:49:22 +0100] "GET / HTTP/1.1" 200 6587 +::1 - - [19/Feb/2025:19:49:23 +0100] "GET /login HTTP/1.1" 302 242 diff --git a/nix/templates/goapp/frontend/sessions.db b/nix/templates/goapp/frontend/sessions.db new file mode 100644 index 0000000..04d6727 Binary files /dev/null and b/nix/templates/goapp/frontend/sessions.db differ diff --git a/nix/templates/goapp/frontend/sqlitestore.go b/nix/templates/goapp/frontend/sqlitestore.go deleted file mode 100644 index 6f59d15..0000000 --- a/nix/templates/goapp/frontend/sqlitestore.go +++ /dev/null @@ -1,284 +0,0 @@ -/* - Gorilla Sessions backend for SQLite. - -Copyright (c) 2013 Contributors. See the list of contributors in the CONTRIBUTORS file for details. - -This software is licensed under a MIT style license available in the LICENSE file. -*/ -package main - -import ( - "database/sql" - "encoding/gob" - "errors" - "fmt" - "log" - "net/http" - "strings" - "time" - - "github.com/gorilla/securecookie" - "github.com/gorilla/sessions" - _ "modernc.org/sqlite" -) - -type SqliteStore struct { - db DB - stmtInsert *sql.Stmt - stmtDelete *sql.Stmt - stmtUpdate *sql.Stmt - stmtSelect *sql.Stmt - - Codecs []securecookie.Codec - Options *sessions.Options - table string -} - -type sessionRow struct { - id string - data string - createdOn time.Time - modifiedOn time.Time - expiresOn time.Time -} - -type DB interface { - Exec(query string, args ...interface{}) (sql.Result, error) - Prepare(query string) (*sql.Stmt, error) - Close() error -} - -func init() { - gob.Register(time.Time{}) -} - -func NewSqliteStore(endpoint string, tableName string, path string, maxAge int, keyPairs ...[]byte) (*SqliteStore, error) { - db, err := sql.Open("sqlite3", endpoint) - if err != nil { - return nil, err - } - - return NewSqliteStoreFromConnection(db, tableName, path, maxAge, keyPairs...) -} - -func NewSqliteStoreFromConnection(db DB, tableName string, path string, maxAge int, keyPairs ...[]byte) (*SqliteStore, error) { - // Make sure table name is enclosed. - tableName = "`" + strings.Trim(tableName, "`") + "`" - - cTableQ := "CREATE TABLE IF NOT EXISTS " + - tableName + " (id INTEGER PRIMARY KEY, " + - "session_data LONGBLOB, " + - "created_on TIMESTAMP DEFAULT 0, " + - "modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " + - "expires_on TIMESTAMP DEFAULT 0);" - if _, err := db.Exec(cTableQ); err != nil { - return nil, err - } - - insQ := "INSERT INTO " + tableName + - "(id, session_data, created_on, modified_on, expires_on) VALUES (NULL, ?, ?, ?, ?)" - stmtInsert, stmtErr := db.Prepare(insQ) - if stmtErr != nil { - return nil, stmtErr - } - - delQ := "DELETE FROM " + tableName + " WHERE id = ?" - stmtDelete, stmtErr := db.Prepare(delQ) - if stmtErr != nil { - return nil, stmtErr - } - - updQ := "UPDATE " + tableName + " SET session_data = ?, created_on = ?, expires_on = ? " + - "WHERE id = ?" - stmtUpdate, stmtErr := db.Prepare(updQ) - if stmtErr != nil { - return nil, stmtErr - } - - selQ := "SELECT id, session_data, created_on, modified_on, expires_on from " + - tableName + " WHERE id = ?" - stmtSelect, stmtErr := db.Prepare(selQ) - if stmtErr != nil { - return nil, stmtErr - } - - return &SqliteStore{ - db: db, - stmtInsert: stmtInsert, - stmtDelete: stmtDelete, - stmtUpdate: stmtUpdate, - stmtSelect: stmtSelect, - Codecs: securecookie.CodecsFromPairs(keyPairs...), - Options: &sessions.Options{ - Path: path, - MaxAge: maxAge, - }, - table: tableName, - }, nil -} - -func (m *SqliteStore) Close() { - m.stmtSelect.Close() - m.stmtUpdate.Close() - m.stmtDelete.Close() - m.stmtInsert.Close() - m.db.Close() -} - -func (m *SqliteStore) Get(r *http.Request, name string) (*sessions.Session, error) { - return sessions.GetRegistry(r).Get(m, name) -} - -func (m *SqliteStore) New(r *http.Request, name string) (*sessions.Session, error) { - session := sessions.NewSession(m, name) - session.Options = &sessions.Options{ - Path: m.Options.Path, - MaxAge: m.Options.MaxAge, - } - session.IsNew = true - var err error - if cook, errCookie := r.Cookie(name); errCookie == nil { - err = securecookie.DecodeMulti(name, cook.Value, &session.ID, m.Codecs...) - if err == nil { - err = m.load(session) - if err == nil { - session.IsNew = false - } else { - err = nil - } - } - } - return session, err -} - -func (m *SqliteStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { - var err error - if session.ID == "" { - if err = m.insert(session); err != nil { - return err - } - } else if err = m.save(session); err != nil { - return err - } - encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, m.Codecs...) - if err != nil { - return err - } - http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options)) - return nil -} - -func (m *SqliteStore) insert(session *sessions.Session) error { - var createdOn time.Time - var modifiedOn time.Time - var expiresOn time.Time - crOn := session.Values["created_on"] - if crOn == nil { - createdOn = time.Now() - } else { - createdOn = crOn.(time.Time) - } - modifiedOn = createdOn - exOn := session.Values["expires_on"] - if exOn == nil { - expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) - } else { - expiresOn = exOn.(time.Time) - } - delete(session.Values, "created_on") - delete(session.Values, "expires_on") - delete(session.Values, "modified_on") - - encoded, encErr := securecookie.EncodeMulti(session.Name(), session.Values, m.Codecs...) - if encErr != nil { - return encErr - } - res, insErr := m.stmtInsert.Exec(encoded, createdOn, modifiedOn, expiresOn) - if insErr != nil { - return insErr - } - lastInserted, lInsErr := res.LastInsertId() - if lInsErr != nil { - return lInsErr - } - session.ID = fmt.Sprintf("%d", lastInserted) - return nil -} - -func (m *SqliteStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { - - // Set cookie to expire. - options := *session.Options - options.MaxAge = -1 - http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options)) - // Clear session values. - for k := range session.Values { - delete(session.Values, k) - } - - _, delErr := m.stmtDelete.Exec(session.ID) - if delErr != nil { - return delErr - } - return nil -} - -func (m *SqliteStore) save(session *sessions.Session) error { - if session.IsNew == true { - return m.insert(session) - } - var createdOn time.Time - var expiresOn time.Time - crOn := session.Values["created_on"] - if crOn == nil { - createdOn = time.Now() - } else { - createdOn = crOn.(time.Time) - } - - exOn := session.Values["expires_on"] - if exOn == nil { - expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) - log.Print("nil") - } else { - expiresOn = exOn.(time.Time) - if expiresOn.Sub(time.Now().Add(time.Second*time.Duration(session.Options.MaxAge))) < 0 { - expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) - } - } - - delete(session.Values, "created_on") - delete(session.Values, "expires_on") - delete(session.Values, "modified_on") - encoded, encErr := securecookie.EncodeMulti(session.Name(), session.Values, m.Codecs...) - if encErr != nil { - return encErr - } - _, updErr := m.stmtUpdate.Exec(encoded, createdOn, expiresOn, session.ID) - if updErr != nil { - return updErr - } - return nil -} - -func (m *SqliteStore) load(session *sessions.Session) error { - row := m.stmtSelect.QueryRow(session.ID) - sess := sessionRow{} - scanErr := row.Scan(&sess.id, &sess.data, &sess.createdOn, &sess.modifiedOn, &sess.expiresOn) - if scanErr != nil { - return scanErr - } - if sess.expiresOn.Sub(time.Now()) < 0 { - log.Printf("Session expired on %s, but it is %s now.", sess.expiresOn, time.Now()) - return errors.New("Session expired") - } - err := securecookie.DecodeMulti(session.Name(), sess.data, &session.Values, m.Codecs...) - if err != nil { - return err - } - session.Values["created_on"] = sess.createdOn - session.Values["modified_on"] = sess.modifiedOn - session.Values["expires_on"] = sess.expiresOn - return nil - -} diff --git a/nix/templates/goapp/frontend/src/db.go b/nix/templates/goapp/frontend/src/db.go new file mode 100644 index 0000000..fd3605a --- /dev/null +++ b/nix/templates/goapp/frontend/src/db.go @@ -0,0 +1,37 @@ +package main + +import ( + "database/sql" + "log" + + _ "github.com/mattn/go-sqlite3" +) + +const create string = ` +CREATE TABLE IF NOT EXISTS users ( + id INTEGER NOT NULL PRIMARY KEY, + created_at DATETIME NOT NULL, + name TEXT, + passwordHash TEXT +); +` + +type State struct { + db *sql.DB // the database storing the "business data" + sessions *SqliteStore // the database storing sessions +} + +func NewState() (*State, error) { + db, err := sql.Open("sqlite3", databasePath) + if err != nil { + log.Println("Error opening the db: ", err) + return nil, err + } + if _, err := db.Exec(create); err != nil { + log.Println("Error creating the tables: ", err) + return nil, err + } + return &State{ + db: db, + }, nil +} diff --git a/nix/templates/goapp/frontend/src/handlers.go b/nix/templates/goapp/frontend/src/handlers.go new file mode 100644 index 0000000..8fdd325 --- /dev/null +++ b/nix/templates/goapp/frontend/src/handlers.go @@ -0,0 +1,236 @@ +package main + +import ( + "fmt" + "html/template" + "log" + "net/http" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/gorilla/sessions" + "golang.org/x/oauth2" +) + +func indexHandler(w http.ResponseWriter, r *http.Request) { + session, err := globalState.sessions.Get(r, "session") + if err != nil { + log.Println("error getting the session") + } + + tpl := indexTplData{ + Error: r.FormValue("error"), + } + + tpl.Breadcrumbs = []Breadcrumb{ + { + Link{"a", "b"}, + []Link{ + {"c", "d"}, + {"e", "f"}, + }, + }, + { + Link{"g", "h"}, + []Link{ + {"i", "j"}, + {"k", "l"}, + }, + }, + } + tpl.NextLinks = []Link{ + {"Login", "/login"}, + } + + if logged, ok := session.Values["logged"].(bool); ok && logged { + tpl.LoggedIn = true + tpl.Claims.IDToken = session.Values["id_token"].(Claims) + tpl.Claims.UserInfo = session.Values["userinfo"].(Claims) + + if len(options.GroupsFilter) >= 1 { + for _, group := range tpl.Claims.UserInfo.Groups { + if isStringInSlice(group, options.GroupsFilter) { + tpl.Groups = append(tpl.Groups, filterText(group, options.Filters)) + } + } + } else { + tpl.Groups = filterSliceOfText(tpl.Claims.UserInfo.Groups, options.Filters) + } + + tpl.Claims.IDToken.PreferredUsername = filterText(tpl.Claims.IDToken.PreferredUsername, options.Filters) + tpl.Claims.UserInfo.PreferredUsername = filterText(tpl.Claims.UserInfo.PreferredUsername, options.Filters) + tpl.Claims.IDToken.Audience = filterSliceOfText(tpl.Claims.IDToken.Audience, options.Filters) + tpl.Claims.UserInfo.Audience = filterSliceOfText(tpl.Claims.UserInfo.Audience, options.Filters) + tpl.Claims.IDToken.Issuer = filterText(tpl.Claims.IDToken.Issuer, options.Filters) + tpl.Claims.UserInfo.Issuer = filterText(tpl.Claims.UserInfo.Issuer, options.Filters) + tpl.Claims.IDToken.Email = filterText(tpl.Claims.IDToken.Email, options.Filters) + tpl.Claims.UserInfo.Email = filterText(tpl.Claims.UserInfo.Email, options.Filters) + tpl.Claims.IDToken.Name = filterText(tpl.Claims.IDToken.Name, options.Filters) + tpl.Claims.UserInfo.Name = filterText(tpl.Claims.UserInfo.Name, options.Filters) + tpl.RawToken = rawTokens[tpl.Claims.IDToken.JWTIdentifier] + tpl.AuthorizeCodeURL = acURLs[tpl.Claims.IDToken.JWTIdentifier].String() + } + + w.Header().Add("Content-Type", "text/html") + + // get the template + t, err := template.New("index").Funcs(templateFuncMap).ParseGlob(fmt.Sprintf("%s/*.html", options.TemplatesPath)) + if err != nil { + log.Printf("Error reading the template Path: %s/*.html", options.TemplatesPath) + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Error reading template file")) + return + } + + // exec! + err = t.ExecuteTemplate(w, "index", tpl) + if err != nil { + log.Println(err) + } +} + +func loginHandler(w http.ResponseWriter, r *http.Request) { + log.Println("[ ] Getting the global session from the session cookie:") + session, err := globalState.sessions.Get(r, options.CookieName) + if err != nil { + log.Println("[ ] Error getting the cookie") + writeErr(w, nil, "error getting cookie", http.StatusInternalServerError) + return + } + + log.Println("[ ] Setting the redirect URL") + session.Values["redirect-url"] = "/" + + log.Println("[ ] Saving the session") + if err = session.Save(r, w); err != nil { + writeErr(w, err, "error saving session", http.StatusInternalServerError) + return + } + + log.Printf("[ ] Redirecting to %s", oauth2Config.AuthCodeURL("random-string")) + http.Redirect(w, r, oauth2Config.AuthCodeURL("random-string-here"), http.StatusFound) +} + +func logoutHandler(w http.ResponseWriter, r *http.Request) { + session, err := globalState.sessions.Get(r, options.CookieName) + if err != nil { + writeErr(w, err, "error getting cookie", http.StatusInternalServerError) + return + } + + // wet the session + session.Values = make(map[interface{}]interface{}) + + if err = session.Save(r, w); err != nil { + writeErr(w, err, "error saving session", http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusFound) +} + +func oauthCallbackHandler(res http.ResponseWriter, req *http.Request) { + log.Println("hit the oauth callback handler") + if req.FormValue("error") != "" { + log.Printf("got an error from the idp: %s", req.FormValue("error")) + http.Redirect(res, req, fmt.Sprintf("/error?%s", req.Form.Encode()), http.StatusFound) + + return + } + + var ( + token *oauth2.Token + idToken *oidc.IDToken + err error + idTokenRaw string + ok bool + ) + + // The state should be checked here in production + if token, err = oauth2Config.Exchange(req.Context(), req.URL.Query().Get("code")); err != nil { + log.Println("Unable to exchange authorization code for tokens") + writeErr(res, err, "unable to exchange authorization code for tokens", http.StatusInternalServerError) + return + } + + // Extract the ID Token from OAuth2 token. + if idTokenRaw, ok = token.Extra("id_token").(string); !ok { + log.Println("missing id token") + writeErr(res, nil, "missing id token", http.StatusInternalServerError) + return + } + + // Parse and verify ID Token payload. + if idToken, err = verifier.Verify(req.Context(), idTokenRaw); err != nil { + log.Printf("unable to verify id token or token is invalid: %+v", idTokenRaw) + writeErr(res, err, "unable to verify id token or token is invalid", http.StatusInternalServerError) + return + } + + // Extract custom claims + claimsIDToken := Claims{} + + if err = idToken.Claims(&claimsIDToken); err != nil { + log.Printf("unable to decode id token claims: %+v", &claimsIDToken) + writeErr(res, err, "unable to decode id token claims", http.StatusInternalServerError) + return + } + + var userinfo *oidc.UserInfo + + if userinfo, err = provider.UserInfo(req.Context(), oauth2.StaticTokenSource(token)); err != nil { + log.Printf("unable to retreive userinfo claims") + writeErr(res, err, "unable to retrieve userinfo claims", http.StatusInternalServerError) + return + } + + claimsUserInfo := Claims{} + + if err = userinfo.Claims(&claimsUserInfo); err != nil { + log.Printf("unable to decode userinfo claims") + writeErr(res, err, "unable to decode userinfo claims", http.StatusInternalServerError) + return + } + + var session *sessions.Session + + if session, err = globalState.sessions.Get(req, options.CookieName); err != nil { + log.Printf("unable to get session from cookie") + writeErr(res, err, "unable to get session from cookie", http.StatusInternalServerError) + return + } + + session.Values["id_token"] = claimsIDToken + session.Values["userinfo"] = claimsUserInfo + session.Values["logged"] = true + rawTokens[claimsIDToken.JWTIdentifier] = idTokenRaw + acURLs[claimsIDToken.JWTIdentifier] = req.URL + + if err = session.Save(req, res); err != nil { + log.Printf("unable to save session") + writeErr(res, err, "unable to save session", http.StatusInternalServerError) + return + } + + var redirectUrl string + + if redirectUrl, ok = session.Values["redirect-url"].(string); ok { + log.Printf("all fine!") + http.Redirect(res, req, redirectUrl, http.StatusFound) + return + } + + http.Redirect(res, req, "/", http.StatusFound) +} + +func writeErr(w http.ResponseWriter, err error, msg string, statusCode int) { + switch { + case err == nil: + log.Println(msg) + http.Error(w, msg, statusCode) + default: + log.Println(msg) + log.Println(err) + http.Error(w, fmt.Errorf("%s: %w", msg, err).Error(), statusCode) + } +} diff --git a/nix/templates/goapp/frontend/src/init.go b/nix/templates/goapp/frontend/src/init.go new file mode 100644 index 0000000..97e58f0 --- /dev/null +++ b/nix/templates/goapp/frontend/src/init.go @@ -0,0 +1,76 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/url" + "os" + "strings" + + "github.com/coreos/go-oidc/v3/oidc" + "golang.org/x/oauth2" +) + +func logInit() loggingMiddleware { + log.Println("[i] Setting up logging...") + logFile, err := os.OpenFile(options.LogFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664) + if err != nil { + log.Fatal("Error opening the server.log file: ", err) + } + return loggingMiddleware{logFile} +} + +func dbInit() { + log.Println("[i] Setting up Global State Struct...") + s, err := NewState() + if err != nil { + log.Fatal("Error creating the NewState(): ", err) + } + globalState = s +} + +func sessionInit() { + log.Println("[i] Setting up Session Storage...") + store, err := NewSqliteStore( + sessiondbPath, + "sessions", + "/", + 3600, + []byte(os.Getenv("SESSION_KEY"))) + if err != nil { + panic(err) + } + globalState.sessions = store +} + +func oauth2Init() (err error) { + log.Println("[i] Setting up oauth2...") + var redirectURL *url.URL + if _, redirectURL, err = getURLs(options.PublicURL); err != nil { + return fmt.Errorf("could not parse public url: %w", err) + } + + log.Printf("[ ] provider_url: %s", options.Issuer) + log.Printf("[ ] redirect_url: %s", redirectURL.String()) + + if provider, err = oidc.NewProvider(context.Background(), options.Issuer); err != nil { + log.Println("Error init oidc provider: ", err) + return fmt.Errorf("error initializing oidc provider: %w", err) + } + + verifier = provider.Verifier(&oidc.Config{ClientID: options.ClientID}) + log.Printf("[ ] ClientID: %s", options.ClientID) + log.Printf("[ ] ClientSecret: %s", options.ClientSecret) + log.Printf("[ ] redirectURL: %s", redirectURL.String()) + log.Printf("[ ] providerEndpoint: %+v", provider.Endpoint()) + log.Printf("[ ] Scopes: %s", options.Scopes) + oauth2Config = oauth2.Config{ + ClientID: options.ClientID, + ClientSecret: options.ClientSecret, + RedirectURL: redirectURL.String(), + Endpoint: provider.Endpoint(), + Scopes: strings.Split(options.Scopes, ","), + } + return nil +} diff --git a/nix/templates/goapp/frontend/src/log.go b/nix/templates/goapp/frontend/src/log.go new file mode 100644 index 0000000..5af719a --- /dev/null +++ b/nix/templates/goapp/frontend/src/log.go @@ -0,0 +1,34 @@ +package main + +import ( + "net/http" + "os" + + "github.com/gorilla/handlers" +) + +// Defines a middleware containing a logfile +// +// This is done to combine gorilla/handlers with gorilla/mux middlewares to +// just use r.Use(logger.Middleware) once instead of adding this to all +// handlers manually (Yes, I'm really missing macros in Go...) +type loggingMiddleware struct { + logFile *os.File +} + +func (l *loggingMiddleware) Middleware(next http.Handler) http.Handler { + return handlers.LoggingHandler(l.logFile, next) +} + +func authMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"] + + if username == nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + } else { + next.ServeHTTP(w, r) + } + }) +} diff --git a/nix/templates/goapp/frontend/src/main.go b/nix/templates/goapp/frontend/src/main.go new file mode 100644 index 0000000..fcf4224 --- /dev/null +++ b/nix/templates/goapp/frontend/src/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "crypto/tls" + "fmt" + "log" + "net/http" + "net/url" + "time" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/gorilla/mux" + "github.com/spf13/cobra" + "golang.org/x/oauth2" +) + +var ( + host string + port int + databasePath string + logFilePath string + sessiondbPath string + templatesPath string + globalState *State + + options Options + oauth2Config oauth2.Config + provider *oidc.Provider + verifier *oidc.IDTokenVerifier + + rawTokens = make(map[string]string) + acURLs = make(map[string]*url.URL) +) + +func main() { + + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + + rootCmd := &cobra.Command{Use: "goapp", RunE: root} + + rootCmd.Flags().StringVar(&options.Host, "host", "0.0.0.0", "Specifies the tcp host to listen on") + rootCmd.Flags().IntVar(&options.Port, "port", 8080, "Specifies the port to listen on") + rootCmd.Flags().StringVar(&options.PublicURL, "public-url", "http://localhost:8080/", "Specifies the root URL to generate the redirect URI") + rootCmd.Flags().StringVar(&options.ClientID, "id", "", "Specifies the OpenID Connect Client ID") + rootCmd.Flags().StringVarP(&options.ClientSecret, "secret", "s", "", "Specifies the OpenID Connect Client Secret") + rootCmd.Flags().StringVarP(&options.Issuer, "issuer", "i", "", "Specifies the URL for the OpenID Connect OP") + rootCmd.Flags().StringVar(&options.Scopes, "scopes", "openid,profile,email,groups", "Specifies the OpenID Connect scopes to request") + rootCmd.Flags().StringVar(&options.CookieName, "cookie-name", "oidc-client", "Specifies the storage cookie name to use") + rootCmd.Flags().StringSliceVar(&options.Filters, "filters", []string{}, "If specified filters the specified text from html output (not json) out of the email addresses, display names, audience, etc") + rootCmd.Flags().StringSliceVar(&options.GroupsFilter, "groups-filter", []string{}, "If specified only shows the groups in this list") + rootCmd.Flags().StringVar(&options.LogFilePath, "logpath", "./server.log", "Specifies the path to store the server logs at") + rootCmd.Flags().StringVar(&options.TemplatesPath, "templatespath", "./templates", "Specifies the path to where the templates are stored") + + _ = rootCmd.MarkFlagRequired("id") + _ = rootCmd.MarkFlagRequired("secret") + _ = rootCmd.MarkFlagRequired("issuer") + + if err := rootCmd.Execute(); err != nil { + log.Fatal(err) + } +} + +func root(cmd *cobra.Command, args []string) (err error) { + + logger := logInit() + oauth2Init() + dbInit() + sessionInit() + + r := mux.NewRouter() + r.Use(logger.Middleware) + r.HandleFunc("/", indexHandler) + r.HandleFunc("/login", loginHandler) + // r.HandleFunc("/logout", ) + // r.HandleFunc("/error", loginHandler) + r.HandleFunc("/oauth2/callback", oauthCallbackHandler) + // r.HandleFunc("/json", loginHandler) + // r.HandleFunc("/jwt.json", loginHandler) + + // endpoints with auth needed + auth_needed := r.PathPrefix("/").Subrouter() + auth_needed.Use(authMiddleware) + auth_needed.HandleFunc("/logout", logoutHandler) + + serverAddress := fmt.Sprintf("%s:%d", options.Host, options.Port) + srv := &http.Server{ + Handler: r, + Addr: serverAddress, + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Printf("[i] Running the server on %s", serverAddress) + log.Fatal(srv.ListenAndServe()) + return +} diff --git a/nix/templates/goapp/frontend/src/sqlitestore.go b/nix/templates/goapp/frontend/src/sqlitestore.go new file mode 100644 index 0000000..34e31e4 --- /dev/null +++ b/nix/templates/goapp/frontend/src/sqlitestore.go @@ -0,0 +1,285 @@ +/* + Gorilla Sessions backend for SQLite. + +Copyright (c) 2013 Contributors. See the list of contributors in the CONTRIBUTORS file for details. + +This software is licensed under a MIT style license available in the LICENSE file. +*/ +package main + +import ( + "database/sql" + "encoding/gob" + "errors" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/gorilla/securecookie" + "github.com/gorilla/sessions" + _ "modernc.org/sqlite" +) + +type SqliteStore struct { + db DB + stmtInsert *sql.Stmt + stmtDelete *sql.Stmt + stmtUpdate *sql.Stmt + stmtSelect *sql.Stmt + + Codecs []securecookie.Codec + Options *sessions.Options + table string +} + +type sessionRow struct { + id string + data string + createdOn time.Time + modifiedOn time.Time + expiresOn time.Time +} + +type DB interface { + Exec(query string, args ...interface{}) (sql.Result, error) + Prepare(query string) (*sql.Stmt, error) + Close() error +} + +func init() { + gob.Register(time.Time{}) + gob.Register(Claims{}) +} + +func NewSqliteStore(endpoint string, tableName string, path string, maxAge int, keyPairs ...[]byte) (*SqliteStore, error) { + db, err := sql.Open("sqlite3", endpoint) + if err != nil { + return nil, err + } + + return NewSqliteStoreFromConnection(db, tableName, path, maxAge, keyPairs...) +} + +func NewSqliteStoreFromConnection(db DB, tableName string, path string, maxAge int, keyPairs ...[]byte) (*SqliteStore, error) { + // Make sure table name is enclosed. + tableName = "`" + strings.Trim(tableName, "`") + "`" + + cTableQ := "CREATE TABLE IF NOT EXISTS " + + tableName + " (id INTEGER PRIMARY KEY, " + + "session_data LONGBLOB, " + + "created_on TIMESTAMP DEFAULT 0, " + + "modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " + + "expires_on TIMESTAMP DEFAULT 0);" + if _, err := db.Exec(cTableQ); err != nil { + return nil, err + } + + insQ := "INSERT INTO " + tableName + + "(id, session_data, created_on, modified_on, expires_on) VALUES (NULL, ?, ?, ?, ?)" + stmtInsert, stmtErr := db.Prepare(insQ) + if stmtErr != nil { + return nil, stmtErr + } + + delQ := "DELETE FROM " + tableName + " WHERE id = ?" + stmtDelete, stmtErr := db.Prepare(delQ) + if stmtErr != nil { + return nil, stmtErr + } + + updQ := "UPDATE " + tableName + " SET session_data = ?, created_on = ?, expires_on = ? " + + "WHERE id = ?" + stmtUpdate, stmtErr := db.Prepare(updQ) + if stmtErr != nil { + return nil, stmtErr + } + + selQ := "SELECT id, session_data, created_on, modified_on, expires_on from " + + tableName + " WHERE id = ?" + stmtSelect, stmtErr := db.Prepare(selQ) + if stmtErr != nil { + return nil, stmtErr + } + + return &SqliteStore{ + db: db, + stmtInsert: stmtInsert, + stmtDelete: stmtDelete, + stmtUpdate: stmtUpdate, + stmtSelect: stmtSelect, + Codecs: securecookie.CodecsFromPairs(keyPairs...), + Options: &sessions.Options{ + Path: path, + MaxAge: maxAge, + }, + table: tableName, + }, nil +} + +func (m *SqliteStore) Close() { + m.stmtSelect.Close() + m.stmtUpdate.Close() + m.stmtDelete.Close() + m.stmtInsert.Close() + m.db.Close() +} + +func (m *SqliteStore) Get(r *http.Request, name string) (*sessions.Session, error) { + return sessions.GetRegistry(r).Get(m, name) +} + +func (m *SqliteStore) New(r *http.Request, name string) (*sessions.Session, error) { + session := sessions.NewSession(m, name) + session.Options = &sessions.Options{ + Path: m.Options.Path, + MaxAge: m.Options.MaxAge, + } + session.IsNew = true + var err error + if cook, errCookie := r.Cookie(name); errCookie == nil { + err = securecookie.DecodeMulti(name, cook.Value, &session.ID, m.Codecs...) + if err == nil { + err = m.load(session) + if err == nil { + session.IsNew = false + } else { + err = nil + } + } + } + return session, err +} + +func (m *SqliteStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { + var err error + if session.ID == "" { + if err = m.insert(session); err != nil { + return err + } + } else if err = m.save(session); err != nil { + return err + } + encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, m.Codecs...) + if err != nil { + return err + } + http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options)) + return nil +} + +func (m *SqliteStore) insert(session *sessions.Session) error { + var createdOn time.Time + var modifiedOn time.Time + var expiresOn time.Time + crOn := session.Values["created_on"] + if crOn == nil { + createdOn = time.Now() + } else { + createdOn = crOn.(time.Time) + } + modifiedOn = createdOn + exOn := session.Values["expires_on"] + if exOn == nil { + expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) + } else { + expiresOn = exOn.(time.Time) + } + delete(session.Values, "created_on") + delete(session.Values, "expires_on") + delete(session.Values, "modified_on") + + encoded, encErr := securecookie.EncodeMulti(session.Name(), session.Values, m.Codecs...) + if encErr != nil { + return encErr + } + res, insErr := m.stmtInsert.Exec(encoded, createdOn, modifiedOn, expiresOn) + if insErr != nil { + return insErr + } + lastInserted, lInsErr := res.LastInsertId() + if lInsErr != nil { + return lInsErr + } + session.ID = fmt.Sprintf("%d", lastInserted) + return nil +} + +func (m *SqliteStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { + + // Set cookie to expire. + options := *session.Options + options.MaxAge = -1 + http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options)) + // Clear session values. + for k := range session.Values { + delete(session.Values, k) + } + + _, delErr := m.stmtDelete.Exec(session.ID) + if delErr != nil { + return delErr + } + return nil +} + +func (m *SqliteStore) save(session *sessions.Session) error { + if session.IsNew == true { + return m.insert(session) + } + var createdOn time.Time + var expiresOn time.Time + crOn := session.Values["created_on"] + if crOn == nil { + createdOn = time.Now() + } else { + createdOn = crOn.(time.Time) + } + + exOn := session.Values["expires_on"] + if exOn == nil { + expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) + log.Print("nil") + } else { + expiresOn = exOn.(time.Time) + if expiresOn.Sub(time.Now().Add(time.Second*time.Duration(session.Options.MaxAge))) < 0 { + expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) + } + } + + delete(session.Values, "created_on") + delete(session.Values, "expires_on") + delete(session.Values, "modified_on") + encoded, encErr := securecookie.EncodeMulti(session.Name(), session.Values, m.Codecs...) + if encErr != nil { + return encErr + } + _, updErr := m.stmtUpdate.Exec(encoded, createdOn, expiresOn, session.ID) + if updErr != nil { + return updErr + } + return nil +} + +func (m *SqliteStore) load(session *sessions.Session) error { + row := m.stmtSelect.QueryRow(session.ID) + sess := sessionRow{} + scanErr := row.Scan(&sess.id, &sess.data, &sess.createdOn, &sess.modifiedOn, &sess.expiresOn) + if scanErr != nil { + return scanErr + } + if sess.expiresOn.Sub(time.Now()) < 0 { + log.Printf("Session expired on %s, but it is %s now.", sess.expiresOn, time.Now()) + return errors.New("Session expired") + } + err := securecookie.DecodeMulti(session.Name(), sess.data, &session.Values, m.Codecs...) + if err != nil { + return err + } + session.Values["created_on"] = sess.createdOn + session.Values["modified_on"] = sess.modifiedOn + session.Values["expires_on"] = sess.expiresOn + return nil + +} diff --git a/nix/templates/goapp/frontend/src/templates.go b/nix/templates/goapp/frontend/src/templates.go new file mode 100644 index 0000000..5ae9397 --- /dev/null +++ b/nix/templates/goapp/frontend/src/templates.go @@ -0,0 +1,42 @@ +package main + +import ( + "html/template" + "strings" +) + +var ( + templateFuncMap = template.FuncMap{ + "stringsJoin": strings.Join, + "stringsEqualFold": strings.EqualFold, + "isStringInSlice": isStringInSlice, + } +) + +type indexTplData struct { + Title, Description, RawToken string + + Breadcrumbs []Breadcrumb + NextLinks []Link + + Error string + LoggedIn bool + Claims tplClaims + Groups []string + AuthorizeCodeURL string +} + +type Link struct { + Name string + Target string +} + +type Breadcrumb struct { + Main Link + Options []Link +} + +type tplClaims struct { + IDToken Claims + UserInfo Claims +} diff --git a/nix/templates/goapp/frontend/src/types.go b/nix/templates/goapp/frontend/src/types.go new file mode 100644 index 0000000..7efcc70 --- /dev/null +++ b/nix/templates/goapp/frontend/src/types.go @@ -0,0 +1,65 @@ +package main + +type Claims struct { + JWTIdentifier string `json:"jti"` + Issuer string `json:"iss"` + Subject string `json:"sub"` + Nonce string `json:"nonce"` + Expires int64 `json:"exp"` + IssueTime int64 `json:"iat"` + RequestedAt int64 `json:"rat"` + AuthorizeTime int64 `json:"auth_time"` + NotBefore int64 `json:"nbf"` + Audience []string `json:"aud"` + Scope []string `json:"scp"` + ScopeString string `json:"scope"` + AccessTokenHash string `json:"at_hash"` + CodeHash string `json:"c_hash"` + AuthenticationContextClassReference string `json:"acr"` + AuthenticationMethodsReference []string `json:"amr"` + + Name string `json:"name"` + GivenName string `json:"given_name"` + FamilyName string `json:"family_name"` + MiddleName string `json:"middle_name"` + Nickname string `json:"nickname"` + PreferredUsername string `json:"preferred_username"` + Profile string `jsoon:"profile"` + Picture string `json:"picture"` + Website string `json:"website"` + Gender string `json:"gender"` + Birthdate string `json:"birthdate"` + ZoneInfo string `json:"zoneinfo"` + Locale string `json:"locale"` + UpdatedAt int64 `json:"updated_at"` + Email string `json:"email"` + EmailAlts []string `json:"alt_emails"` + EmailVerified bool `json:"email_verified"` + PhoneNumber string `json:"phone_number"` + PhoneNumberVerified bool `json:"phone_number_verified"` + Address ClamsAddress `json:"address"` + Groups []string `json:"groups"` +} + +type ClamsAddress struct { + StreetAddress string `json:"street_address"` + Locality string `json:"locality"` + Region string `json:"region"` + PostalCode string `json:"postal_code"` + Country string `json:"country"` +} + +type Options struct { + Host string + Port int + LogFilePath string + TemplatesPath string + ClientID string + ClientSecret string + Issuer string + PublicURL string + Scopes string + CookieName string + Filters []string + GroupsFilter []string +} diff --git a/nix/templates/goapp/frontend/src/util.go b/nix/templates/goapp/frontend/src/util.go new file mode 100644 index 0000000..89d28ba --- /dev/null +++ b/nix/templates/goapp/frontend/src/util.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "net/url" + "path" + "strings" +) + +func isStringInSlice(s string, slice []string) bool { + for _, x := range slice { + if s == x { + return true + } + } + + return false +} + +func filterText(input string, filters []string) (output string) { + if len(filters) == 0 { + return input + } + + for _, filter := range filters { + input = strings.Replace(input, filter, strings.Repeat("*", len(filter)), -1) + } + + return input +} + +func filterSliceOfText(input []string, filters []string) (output []string) { + for _, item := range input { + output = append(output, filterText(item, filters)) + } + + return output +} + +func getURLs(rootURL string) (publicURL *url.URL, redirectURL *url.URL, err error) { + if publicURL, err = url.Parse(rootURL); err != nil { + return nil, nil, err + } + + if publicURL.Scheme != "http" && publicURL.Scheme != "https" { + return nil, nil, fmt.Errorf("scheme must be http or https but it is '%s'", publicURL.Scheme) + } + + if !strings.HasSuffix(publicURL.Path, "/") { + publicURL.Path += "/" + } + + redirectURL = &url.URL{} + *redirectURL = *publicURL + redirectURL.Path = path.Join(redirectURL.Path, "/oauth2/callback") + + return publicURL, redirectURL, nil +} diff --git a/nix/templates/goapp/frontend/templates/footer.html b/nix/templates/goapp/frontend/templates/footer.html new file mode 100644 index 0000000..1899096 --- /dev/null +++ b/nix/templates/goapp/frontend/templates/footer.html @@ -0,0 +1,8 @@ +{{ define "footer" }} +{{ if .asd }} +



+{{ . }} +{{ end }} + +{{ end }} + diff --git a/nix/templates/goapp/frontend/templates/head.html b/nix/templates/goapp/frontend/templates/head.html new file mode 100644 index 0000000..efc7ce3 --- /dev/null +++ b/nix/templates/goapp/frontend/templates/head.html @@ -0,0 +1,153 @@ +{{ define "head" }} + + + + + + + goapp + + + +{{ end }} diff --git a/nix/templates/goapp/frontend/templates/index.html b/nix/templates/goapp/frontend/templates/index.html new file mode 100644 index 0000000..1d21f3d --- /dev/null +++ b/nix/templates/goapp/frontend/templates/index.html @@ -0,0 +1,81 @@ +{{ define "index" }} + +{{ template "head" . }} +{{ template "nav" . }} + +

goapp

+ +{{- if .LoggedIn }} +

Logged in as {{ or .Claims.UserInfo.PreferredUsername .Claims.IDToken.Subject "unknown" }}!

+

Log out

+

Access Token Hash: {{ .Claims.IDToken.AccessTokenHash }}

+

Code Hash: {{ .Claims.IDToken.CodeHash }}

+

Authentication Context Class Reference: {{ .Claims.IDToken.AuthenticationContextClassReference }}

+

Authentication Methods Reference: {{ stringsJoin .Claims.IDToken.AuthenticationMethodsReference ", " }}

+

Audience: {{ stringsJoin .Claims.IDToken.Audience ", " }}

+

Expires: {{ .Claims.IDToken.Expires }}

+

Issue Time: {{ .Claims.IDToken.IssueTime }}

+

Requested At: {{ .Claims.IDToken.RequestedAt }}

+

Authorize Time: {{ .Claims.IDToken.AuthorizeTime }}

+

Not Before: {{ .Claims.IDToken.NotBefore }}

+

Issuer: {{ .Claims.IDToken.Issuer }}

+

JWT ID: {{ .Claims.IDToken.JWTIdentifier }}

+

Subject: {{ .Claims.IDToken.Subject }}

+

Nonce: {{ .Claims.IDToken.Nonce }}

+

Name: {{ .Claims.UserInfo.Name }}

+

Name (ID Token): {{ .Claims.IDToken.Name }}

+

Given Name: {{ .Claims.UserInfo.GivenName }}

+

Given Name (ID Token): {{ .Claims.IDToken.GivenName }}

+

Family Name: {{ .Claims.UserInfo.FamilyName }}

+

Family Name (ID Token): {{ .Claims.IDToken.FamilyName }}

+

Middle Name: {{ .Claims.UserInfo.MiddleName }}

+

Middle Name (ID Token): {{ .Claims.IDToken.MiddleName }}

+

Nickname: {{ .Claims.UserInfo.Nickname }}

+

Nickname (ID Token): {{ .Claims.IDToken.Nickname }}

+

Preferred Username: {{ .Claims.UserInfo.PreferredUsername }}

+

Preferred Username (ID Token): {{ .Claims.IDToken.PreferredUsername }}

+

Profile: {{ .Claims.UserInfo.Profile }}

+

Profile (ID Token): {{ .Claims.IDToken.Profile }}

+

Website: {{ .Claims.UserInfo.Website }}

+

Website (ID Token): {{ .Claims.IDToken.Website }}

+

Gender: {{ .Claims.UserInfo.Gender }}

+

Gender (ID Token): {{ .Claims.IDToken.Gender }}

+

Birthdate: {{ .Claims.UserInfo.Birthdate }}

+

Birthdate (ID Token): {{ .Claims.IDToken.Birthdate }}

+

ZoneInfo: {{ .Claims.UserInfo.ZoneInfo }}

+

ZoneInfo (ID Token): {{ .Claims.IDToken.ZoneInfo }}

+

Locale: {{ .Claims.UserInfo.Locale }}

+

Locale (ID Token): {{ .Claims.IDToken.Locale }}

+

Updated At: {{ .Claims.UserInfo.UpdatedAt }}

+

Updated At (ID Token): {{ .Claims.IDToken.UpdatedAt }}

+

Email: {{ .Claims.UserInfo.Email }}

+

Email (ID Token): {{ .Claims.IDToken.Email }}

+

Email Alts: {{ .Claims.UserInfo.EmailAlts }}

+

Email Alts (ID Token): {{ .Claims.IDToken.EmailAlts }}

+

Email Verified: {{ .Claims.UserInfo.EmailVerified }}

+

Email Verified (ID Token): {{ .Claims.IDToken.EmailVerified }}

+

Phone Number: {{ .Claims.UserInfo.PhoneNumber }}

+

Phone Number (ID Token): {{ .Claims.IDToken.PhoneNumber }}

+

Phone Number Verified: {{ .Claims.UserInfo.PhoneNumberVerified }}

+

Phone Number Verified (ID Token): {{ .Claims.IDToken.PhoneNumberVerified }}

+

Groups: {{ stringsJoin .Groups ", " }}

+

Groups (ID Token): {{ stringsJoin .Groups ", " }}

+

Raw: {{ .RawToken }}

+

Authorize Code URL: {{ .AuthorizeCodeURL }}

+{{- else }} +

Not logged yet...

Log in +{{- end }} + + + +{{ end }} + diff --git a/nix/templates/goapp/frontend/templates/login.html b/nix/templates/goapp/frontend/templates/login.html new file mode 100644 index 0000000..6e54781 --- /dev/null +++ b/nix/templates/goapp/frontend/templates/login.html @@ -0,0 +1,41 @@ +{{ define "login" }} + +{{ template "head" . }} + + {{ template "nav" . }} + + +

Login

+ + {{ if .err }}{{ .err }}{{ end }} + {{ if .logged_in }} + Already logged in! Return home + {{ else }} +
+ + + + + + + + + + + + + + + + + + +
{{ .res }}
+
+ + {{ end }} + + + +{{ template "footer" . }} +{{end}} diff --git a/nix/templates/goapp/frontend/templates/nav.html b/nix/templates/goapp/frontend/templates/nav.html new file mode 100644 index 0000000..bf9820f --- /dev/null +++ b/nix/templates/goapp/frontend/templates/nav.html @@ -0,0 +1,41 @@ +{{ define "nav" }} +
+

+ v0.0.1 EARLY BETA - Data can be deleted at random! +

+
+ + +
+{{ end }} + + -- cgit 1.4.1