Updated packages, removed unneeded double-check, switched over to RFC-2024-0008 as the authentication protocol, updated fulgens library, fixed broken quotas, fixed a broken JSON validator

Signed-off-by: Arzumify <jliwin98@danwin1210.de>
This commit is contained in:
Tracker-Friendly 2024-10-13 19:20:19 +01:00
parent 5b6c547b9a
commit 4fe28255fe
8 changed files with 349 additions and 243 deletions

4
go.mod
View File

@ -3,7 +3,7 @@ module git.ailur.dev/ailur/fulgens
go 1.23.1 go 1.23.1
require ( require (
git.ailur.dev/ailur/fg-library v1.0.0 git.ailur.dev/ailur/fg-library v1.0.1
git.ailur.dev/ailur/fg-nucleus-library v1.0.0 git.ailur.dev/ailur/fg-nucleus-library v1.0.0
git.ailur.dev/ailur/pow v1.0.0 git.ailur.dev/ailur/pow v1.0.0
github.com/cespare/xxhash/v2 v2.3.0 github.com/cespare/xxhash/v2 v2.3.0
@ -27,7 +27,7 @@ require (
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/stretchr/testify v1.9.0 // indirect github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/net v0.30.0 // indirect golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect

14
go.sum
View File

@ -1,5 +1,5 @@
git.ailur.dev/ailur/fg-library v1.0.0 h1:61RkJW9g4PqAiZFjpwUnx6QUYYSeXJXLjLi1d47NfTA= git.ailur.dev/ailur/fg-library v1.0.1 h1:7TY2shmYNfKPzCTeYC80uj+sFZPbBWeOlqKT6ZsKFmc=
git.ailur.dev/ailur/fg-library v1.0.0/go.mod h1:hOUkxs2rRouSwNnNZlo7CsFVH12kmjqheyzPQ4to1N8= git.ailur.dev/ailur/fg-library v1.0.1/go.mod h1:hOUkxs2rRouSwNnNZlo7CsFVH12kmjqheyzPQ4to1N8=
git.ailur.dev/ailur/fg-nucleus-library v1.0.0 h1:TT1V4cfka+uUpvV1zU7bc4KXFkgnsI/sIvaZDDxXk+k= git.ailur.dev/ailur/fg-nucleus-library v1.0.0 h1:TT1V4cfka+uUpvV1zU7bc4KXFkgnsI/sIvaZDDxXk+k=
git.ailur.dev/ailur/fg-nucleus-library v1.0.0/go.mod h1:m4gNSEypfgrUV8bXaR8NLB8zchUM59y0ellV1wp/C+I= git.ailur.dev/ailur/fg-nucleus-library v1.0.0/go.mod h1:m4gNSEypfgrUV8bXaR8NLB8zchUM59y0ellV1wp/C+I=
git.ailur.dev/ailur/pow v1.0.0 h1:eCJiZSbskcmzmwR4Nv4YrYpsZci5kfoGM9ihkXAHHoU= git.ailur.dev/ailur/pow v1.0.0 h1:eCJiZSbskcmzmwR4Nv4YrYpsZci5kfoGM9ihkXAHHoU=
@ -46,12 +46,10 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
@ -61,8 +59,8 @@ golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=

47
main.go
View File

@ -41,7 +41,7 @@ type Config struct {
Database struct { Database struct {
DatabaseType string `json:"databaseType" validate:"required,oneof=sqlite postgres"` DatabaseType string `json:"databaseType" validate:"required,oneof=sqlite postgres"`
ConnectionString string `json:"connectionString" validate:"required_if=DatabaseType postgres"` ConnectionString string `json:"connectionString" validate:"required_if=DatabaseType postgres"`
DatabasePath string `json:"databasePath" validate:"required_if=DatabaseType sqlite,isDirectory"` DatabasePath string `json:"databasePath" validate:"required_if=DatabaseType sqlite"`
} `json:"database" validate:"required"` } `json:"database" validate:"required"`
Services map[string]interface{} `json:"services"` Services map[string]interface{} `json:"services"`
} }
@ -122,7 +122,10 @@ func processInterServiceMessage(channel chan library.InterServiceMessage, config
ForServiceID: message.ServiceID, ForServiceID: message.ServiceID,
MessageType: 2, MessageType: 2,
SentAt: time.Now(), SentAt: time.Now(),
Message: pluginConn, Message: library.Database{
DB: pluginConn,
DBType: library.Sqlite,
},
} }
} }
} else if config.Database.DatabaseType == "postgres" { } else if config.Database.DatabaseType == "postgres" {
@ -139,7 +142,7 @@ func processInterServiceMessage(channel chan library.InterServiceMessage, config
} }
} else { } else {
// Try to create the schema // Try to create the schema
_, err = conn.Exec("CREATE SCHEMA IF NOT EXISTS " + message.ServiceID.String()) _, err = conn.Exec("CREATE SCHEMA IF NOT EXISTS \"" + message.ServiceID.String() + "\"")
if err != nil { if err != nil {
// Report an error // Report an error
services[message.ServiceID].Inbox <- library.InterServiceMessage{ services[message.ServiceID].Inbox <- library.InterServiceMessage{
@ -151,7 +154,13 @@ func processInterServiceMessage(channel chan library.InterServiceMessage, config
} }
} else { } else {
// Create a new connection to the database // Create a new connection to the database
pluginConn, err := sql.Open("postgres", config.Database.ConnectionString+" dbname="+message.ServiceID.String()) var connectionString string
if strings.Contains(config.Database.ConnectionString, "?") {
connectionString = config.Database.ConnectionString + "&search_path=\"" + message.ServiceID.String() + "\""
} else {
connectionString = config.Database.ConnectionString + "?search_path=\"" + message.ServiceID.String() + "\""
}
pluginConn, err := sql.Open("postgres", connectionString)
if err != nil { if err != nil {
// Report an error // Report an error
services[message.ServiceID].Inbox <- library.InterServiceMessage{ services[message.ServiceID].Inbox <- library.InterServiceMessage{
@ -162,26 +171,16 @@ func processInterServiceMessage(channel chan library.InterServiceMessage, config
Message: err, Message: err,
} }
} else { } else {
// Try to switch schemas // Report a successful activation
_, err = pluginConn.Exec("SET search_path TO " + message.ServiceID.String()) services[message.ServiceID].Inbox <- library.InterServiceMessage{
if err != nil { ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"),
// Report an error ForServiceID: message.ServiceID,
services[message.ServiceID].Inbox <- library.InterServiceMessage{ MessageType: 2,
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), SentAt: time.Now(),
ForServiceID: message.ServiceID, Message: library.Database{
MessageType: 1, DB: pluginConn,
SentAt: time.Now(), DBType: library.Postgres,
Message: err, },
}
} else {
// Report a successful activation
services[message.ServiceID].Inbox <- library.InterServiceMessage{
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"),
ForServiceID: message.ServiceID,
MessageType: 2,
SentAt: time.Now(),
Message: pluginConn,
}
} }
} }
} }

View File

@ -165,7 +165,7 @@ func verifyJwt(token string, publicKey ed25519.PublicKey, mem *sql.DB) ([]byte,
} }
func Main(information library.ServiceInitializationInformation) { func Main(information library.ServiceInitializationInformation) {
var conn *sql.DB var conn library.Database
var mem *sql.DB var mem *sql.DB
var publicKey ed25519.PublicKey var publicKey ed25519.PublicKey
var privateKey ed25519.PrivateKey var privateKey ed25519.PrivateKey
@ -201,40 +201,81 @@ func Main(information library.ServiceInitializationInformation) {
if response.MessageType == 2 { if response.MessageType == 2 {
// This is the connection information // This is the connection information
// Set up the database connection // Set up the database connection
conn = response.Message.(*sql.DB) conn = response.Message.(library.Database)
// Create the global table if conn.DBType == library.Sqlite {
// Uniqueness check is a hack to ensure we only have one global row // Create the global table
_, err := conn.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)") // Uniqueness check is a hack to ensure we only have one global row
if err != nil { _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
logFunc(err.Error(), 3, information) if err != nil {
} logFunc(err.Error(), 3, information)
// Create the users table }
_, err = conn.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, password BLOB NOT NULL, salt BLOB NOT NULL)") // Create the users table
if err != nil { _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BLOB NOT NULL)")
logFunc(err.Error(), 3, information) if err != nil {
} logFunc(err.Error(), 3, information)
// Create the oauth table }
_, err = conn.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BLOB NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')") // Create the oauth table
if err != nil { _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BLOB NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')")
logFunc(err.Error(), 3, information) if err != nil {
logFunc(err.Error(), 3, information)
}
} else {
// Create the global table
// Uniqueness check is a hack to ensure we only have one global row
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BYTEA NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the users table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, password BYTEA NOT NULL, salt BYTEA NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the oauth table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BYTEA NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')")
if err != nil {
logFunc(err.Error(), 3, information)
}
} }
// Set up the in-memory cache // Set up the in-memory cache
mem, err = sql.Open("sqlite", "file:"+ServiceInformation.ServiceID.String()+"?mode=memory&cache=shared") mem, err = sql.Open("sqlite", "file:"+ServiceInformation.ServiceID.String()+"?mode=memory&cache=shared")
if err != nil { if err != nil {
logFunc(err.Error(), 3, information) logFunc(err.Error(), 3, information)
} }
// Drop the tables if they exist
_, err = mem.Exec("DROP TABLE IF EXISTS sessions")
if err != nil {
logFunc(err.Error(), 3, information)
}
_, err = mem.Exec("DROP TABLE IF EXISTS logins")
if err != nil {
logFunc(err.Error(), 3, information)
}
_, err = mem.Exec("DROP TABLE IF EXISTS spent")
if err != nil {
logFunc(err.Error(), 3, information)
}
_, err = mem.Exec("DROP TABLE IF EXISTS challengeResponse")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the sessions table // Create the sessions table
_, err = mem.Exec("CREATE TABLE IF NOT EXISTS sessions (id BLOB NOT NULL, session TEXT NOT NULL, device TEXT NOT NULL DEFAULT '?')") _, err = mem.Exec("CREATE TABLE sessions (id BLOB NOT NULL, session TEXT NOT NULL, device TEXT NOT NULL DEFAULT '?')")
if err != nil { if err != nil {
logFunc(err.Error(), 3, information) logFunc(err.Error(), 3, information)
} }
// Create the logins table // Create the logins table
_, err = mem.Exec("CREATE TABLE IF NOT EXISTS logins (appId TEXT NOT NULL, exchangeCode TEXT NOT NULL UNIQUE, pkce TEXT, pkceMethod TEXT, openid BOOLEAN NOT NULL, userId BLOB NOT NULL UNIQUE, nonce TEXT NOT NULL DEFAULT '', token TEXT NOT NULL)") _, err = mem.Exec("CREATE TABLE logins (appId TEXT NOT NULL, exchangeCode TEXT NOT NULL UNIQUE, pkce TEXT, pkceMethod TEXT, openid BOOLEAN NOT NULL, userId BLOB NOT NULL UNIQUE, nonce TEXT NOT NULL DEFAULT '', token TEXT NOT NULL)")
if err != nil { if err != nil {
logFunc(err.Error(), 3, information) logFunc(err.Error(), 3, information)
} }
// Create the spent PoW table // Create the spent PoW table
_, err = mem.Exec("CREATE TABLE IF NOT EXISTS spent (hash BLOB NOT NULL UNIQUE, expires INTEGER NOT NULL)") _, err = mem.Exec("CREATE TABLE spent (hash BLOB NOT NULL UNIQUE, expires INTEGER NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the challenge-response table
_, err = mem.Exec("CREATE TABLE challengeResponse (challenge TEXT NOT NULL UNIQUE, userId BLOB NOT NULL, expires INTEGER NOT NULL)")
if err != nil { if err != nil {
logFunc(err.Error(), 3, information) logFunc(err.Error(), 3, information)
} }
@ -246,7 +287,7 @@ func Main(information library.ServiceInitializationInformation) {
// Set up the signing keys // Set up the signing keys
// Check if the global table has the keys // Check if the global table has the keys
err = conn.QueryRow("SELECT key FROM global LIMIT 1").Scan(&privateKey) err = conn.DB.QueryRow("SELECT key FROM global LIMIT 1").Scan(&privateKey)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
// Generate a new key // Generate a new key
@ -256,7 +297,7 @@ func Main(information library.ServiceInitializationInformation) {
logFunc(err.Error(), 3, information) logFunc(err.Error(), 3, information)
} }
// Insert the key into the global table // Insert the key into the global table
_, err = conn.Exec("INSERT INTO global (key) VALUES (?)", privateKey) _, err = conn.DB.Exec("INSERT INTO global (key) VALUES ($1)", privateKey)
if err != nil { if err != nil {
logFunc(err.Error(), 3, information) logFunc(err.Error(), 3, information)
} }
@ -268,14 +309,14 @@ func Main(information library.ServiceInitializationInformation) {
} }
// Set up the test app // Set up the test app
_, err = conn.Exec("DELETE FROM oauth WHERE appId = 'TestApp-DoNotUse'") _, err = conn.DB.Exec("DELETE FROM oauth WHERE appId = 'TestApp-DoNotUse'")
if err != nil { if err != nil {
testAppIsAvailable = false testAppIsAvailable = false
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
} }
if testAppIsInternalApp { if testAppIsInternalApp {
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', ?, 'Test App', ?, '[\"openid\", \"clientKeyShare\"]', ?)", serviceIDBytes, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester") _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", serviceIDBytes, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
} else { } else {
testAppCreator, err := uuid.New().MarshalBinary() testAppCreator, err := uuid.New().MarshalBinary()
if err != nil { if err != nil {
@ -283,7 +324,7 @@ func Main(information library.ServiceInitializationInformation) {
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
} }
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', ?, 'Test App', ?, '[\"openid\", \"clientKeyShare\"]', ?)", testAppCreator, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester") _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", testAppCreator, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
} }
if err != nil { if err != nil {
testAppIsAvailable = false testAppIsAvailable = false
@ -336,31 +377,60 @@ func Main(information library.ServiceInitializationInformation) {
}) })
router.Get("/authorize", func(w http.ResponseWriter, r *http.Request) { router.Get("/authorize", func(w http.ResponseWriter, r *http.Request) {
var name string
var creator []byte
if r.URL.Query().Get("client_id") != "" { if r.URL.Query().Get("client_id") != "" {
err := conn.QueryRow("SELECT name, creator FROM oauth WHERE appId = ?", r.URL.Query().Get("client_id")).Scan(&name, &creator) if conn.DBType == library.Sqlite {
if err != nil { var name string
if errors.Is(err, sql.ErrNoRows) { var creator []byte
renderString(404, w, "App not found", information) err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
} else { if err != nil {
logFunc(err.Error(), 2, information) if errors.Is(err, sql.ErrNoRows) {
renderString(500, w, "Sorry, something went wrong on our end. Error code: 03. Please report to the administrator.", information) renderString(404, w, "App not found", information)
} else {
logFunc(err.Error(), 2, information)
renderString(500, w, "Sorry, something went wrong on our end. Error code: 03. Please report to the administrator.", information)
}
return
} }
return
}
}
if !bytes.Equal(creator, serviceIDBytes) { if !bytes.Equal(creator, serviceIDBytes) {
renderTemplate(200, w, map[string]interface{}{ renderTemplate(200, w, map[string]interface{}{
"identifier": identifier, "identifier": identifier,
"name": name, "name": name,
}, "authorize.html", information) }, "authorize.html", information)
} else {
renderTemplate(200, w, map[string]interface{}{
"identifier": identifier,
"name": name,
}, "autoAccept.html", information)
}
} else {
var name string
var creator uuid.UUID
err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
renderString(404, w, "App not found", information)
} else {
logFunc(err.Error(), 2, information)
renderString(500, w, "Sorry, something went wrong on our end. Error code: 03. Please report to the administrator.", information)
}
return
}
if creator != ServiceInformation.ServiceID {
renderTemplate(200, w, map[string]interface{}{
"identifier": identifier,
"name": name,
}, "authorize.html", information)
} else {
renderTemplate(200, w, map[string]interface{}{
"identifier": identifier,
"name": name,
}, "autoAccept.html", information)
}
}
} else { } else {
renderTemplate(200, w, map[string]interface{}{ http.Redirect(w, r, "/dashboard", 301)
"identifier": identifier,
"name": name,
}, "autoAccept.html", information)
} }
}) })
@ -386,7 +456,7 @@ func Main(information library.ServiceInitializationInformation) {
// Check if they have the clientKeyShare scope // Check if they have the clientKeyShare scope
var scopes string var scopes string
err = conn.QueryRow("SELECT scopes FROM oauth WHERE appId = ?", claims["aud"]).Scan(&scopes) err = conn.DB.QueryRow("SELECT scopes FROM oauth WHERE appId = $1", claims["aud"]).Scan(&scopes)
if err != nil { if err != nil {
renderString(500, w, "Sorry, something went wrong on our end. Error code: 20. Please report to the administrator.", information) renderString(500, w, "Sorry, something went wrong on our end. Error code: 20. Please report to the administrator.", information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -472,7 +542,7 @@ func Main(information library.ServiceInitializationInformation) {
hashedPassword := argon2.IDKey(newPassword, salt, 64, 4096, 1, 32) hashedPassword := argon2.IDKey(newPassword, salt, 64, 4096, 1, 32)
// Update the password // Update the password
_, err = conn.Exec("UPDATE users SET password = ?, salt = ? WHERE id = ?", hashedPassword, salt, userId) _, err = conn.DB.Exec("UPDATE users SET password = $1, salt = $2 WHERE id = $3", hashedPassword, salt, userId)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "05"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "05"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -494,8 +564,7 @@ func Main(information library.ServiceInitializationInformation) {
router.Post("/api/signup", func(w http.ResponseWriter, r *http.Request) { router.Post("/api/signup", func(w http.ResponseWriter, r *http.Request) {
type signup struct { type signup struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` PublicKey string `json:"publicKey"`
Salt string `json:"salt"`
ProofOfWork string `json:"proofOfWork"` ProofOfWork string `json:"proofOfWork"`
} }
var data signup var data signup
@ -544,29 +613,22 @@ func Main(information library.ServiceInitializationInformation) {
} }
} }
// Decode the password // Decode the public key
password, err := base64.StdEncoding.DecodeString(data.Password) publicKey, err := base64.StdEncoding.DecodeString(data.PublicKey)
if err != nil {
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
return
}
// Decode the salt
salt, err := base64.StdEncoding.DecodeString(data.Salt)
if err != nil { if err != nil {
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information) renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
return return
} }
// Try to insert the user // Try to insert the user
userId := uuid.New() userID, err := uuid.New().MarshalBinary()
userIdBytes, err := userId.MarshalBinary()
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "08"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "08"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
return return
} }
_, err = conn.Exec("INSERT INTO users (id, created, username, password, salt, created) VALUES (?, ?, ?, ?, ?, ?)", userIdBytes, time.Now().Unix(), data.Username, password, salt, time.Now().Unix())
_, err = conn.DB.Exec("INSERT INTO users (id, created, username, publicKey, created) VALUES ($1, $2, $3, $4, $5)", userID, time.Now().Unix(), data.Username, publicKey, time.Now().Unix())
if err != nil { if err != nil {
if strings.Contains(err.Error(), "UNIQUE constraint failed") { if strings.Contains(err.Error(), "UNIQUE constraint failed") {
renderJSON(409, w, map[string]interface{}{"error": "Username already taken"}, information) renderJSON(409, w, map[string]interface{}{"error": "Username already taken"}, information)
@ -588,7 +650,7 @@ func Main(information library.ServiceInitializationInformation) {
} }
// Insert the session // Insert the session
_, err = mem.Exec("INSERT INTO sessions (id, session, device) VALUES (?, ?, ?)", userIdBytes, session, r.Header.Get("User-Agent")) _, err = mem.Exec("INSERT INTO sessions (id, session, device) VALUES (?, ?, ?)", userID, session, r.Header.Get("User-Agent"))
// Return success, as well as the session token // Return success, as well as the session token
renderJSON(200, w, map[string]interface{}{"key": session}, information) renderJSON(200, w, map[string]interface{}{"key": session}, information)
@ -606,12 +668,12 @@ func Main(information library.ServiceInitializationInformation) {
return return
} }
// Get the salt for the user // Get the id for the user
var salt []byte var userId []byte
err = conn.QueryRow("SELECT salt FROM users WHERE username = ?", data.Username).Scan(&salt) err = conn.DB.QueryRow("SELECT id FROM users WHERE username = $1", data.Username).Scan(&userId)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
renderJSON(401, w, map[string]interface{}{"error": "Invalid username or password"}, information) renderJSON(401, w, map[string]interface{}{"error": "Invalid username"}, information)
} else { } else {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "12"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "12"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -619,14 +681,30 @@ func Main(information library.ServiceInitializationInformation) {
return return
} }
// Return the salt // Generate a new challenge
renderJSON(200, w, map[string]interface{}{"salt": base64.StdEncoding.EncodeToString(salt)}, information) challenge, err := randomChars(512)
if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "53"}, information)
logFunc(err.Error(), 2, information)
return
}
// Insert the challenge with one minute expiration
_, err = mem.Exec("INSERT INTO challengeResponse (challenge, userId, expires) VALUES (?, ?, ?)", challenge, userId, time.Now().Unix()+60)
if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "53"}, information)
logFunc(err.Error(), 2, information)
return
}
// Return the challenge
renderJSON(200, w, map[string]interface{}{"challenge": challenge}, information)
}) })
router.Post("/api/login", func(w http.ResponseWriter, r *http.Request) { router.Post("/api/login", func(w http.ResponseWriter, r *http.Request) {
type login struct { type login struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Signature string `json:"signature"`
} }
var data login var data login
@ -638,11 +716,11 @@ func Main(information library.ServiceInitializationInformation) {
// Try to select the user // Try to select the user
var userId []byte var userId []byte
var hashedPassword []byte var publicKey []byte
err = conn.QueryRow("SELECT id, password FROM users WHERE username = ?", data.Username).Scan(&userId, &hashedPassword) err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &publicKey)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
renderJSON(401, w, map[string]interface{}{"error": "Invalid username or password"}, information) renderJSON(401, w, map[string]interface{}{"error": "Invalid username"}, information)
} else { } else {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "12"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "12"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -650,16 +728,38 @@ func Main(information library.ServiceInitializationInformation) {
return return
} }
// Decode the password // Decode the challenge
password, err := base64.StdEncoding.DecodeString(data.Password) signature, err := base64.StdEncoding.DecodeString(data.Signature)
if err != nil { if err != nil {
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information) renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
return return
} }
// Verify the password // Verify the challenge
if !bytes.Equal(password, hashedPassword) { // Select the current challenge from the database
renderJSON(401, w, map[string]interface{}{"error": "Invalid username or password"}, information) var challenge string
err = mem.QueryRow("SELECT challenge FROM challengeResponse WHERE userId = ?", userId).Scan(&challenge)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
renderJSON(401, w, map[string]interface{}{"error": "Invalid challenge"}, information)
} else {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "52"}, information)
logFunc(err.Error(), 2, information)
}
return
}
// Check if the challenge is correct by verifying the signature
if !ed25519.Verify(publicKey, []byte(challenge), signature) {
renderJSON(401, w, map[string]interface{}{"error": "Invalid signature"}, information)
return
}
// Delete the challenge
_, err = mem.Exec("DELETE FROM challengeResponse WHERE userId = ?", userId)
if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "53"}, information)
logFunc(err.Error(), 2, information)
return return
} }
@ -708,7 +808,7 @@ func Main(information library.ServiceInitializationInformation) {
// Get the username and the creation date // Get the username and the creation date
var username string var username string
var created int64 var created int64
err = conn.QueryRow("SELECT username, created FROM users WHERE id = ?", userId).Scan(&username, &created) err = conn.DB.QueryRow("SELECT username, created FROM users WHERE id = $1", userId).Scan(&username, &created)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "15"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "15"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -771,7 +871,7 @@ func Main(information library.ServiceInitializationInformation) {
// Check if they have the openid scope // Check if they have the openid scope
var scopes string var scopes string
err = conn.QueryRow("SELECT scopes FROM oauth WHERE appId = ?", claims["aud"]).Scan(&scopes) err = conn.DB.QueryRow("SELECT scopes FROM oauth WHERE appId = $1", claims["aud"]).Scan(&scopes)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "17"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "17"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -802,7 +902,7 @@ func Main(information library.ServiceInitializationInformation) {
// Get the username // Get the username
var username string var username string
err := conn.QueryRow("SELECT username FROM users WHERE id = ?", userId).Scan(&username) err := conn.DB.QueryRow("SELECT username FROM users WHERE id = $1", userId).Scan(&username)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "19"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "19"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -833,7 +933,7 @@ func Main(information library.ServiceInitializationInformation) {
// Verify the AppID, redirectUri and scopes // Verify the AppID, redirectUri and scopes
var appId, redirectUri, scopes string var appId, redirectUri, scopes string
err = conn.QueryRow("SELECT appId, redirectUri, scopes FROM oauth WHERE appId = ?", data.AppId).Scan(&appId, &redirectUri, &scopes) err = conn.DB.QueryRow("SELECT appId, redirectUri, scopes FROM oauth WHERE appId = $1", data.AppId).Scan(&appId, &redirectUri, &scopes)
if err != nil { if err != nil {
renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information) renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information)
return return
@ -979,7 +1079,7 @@ func Main(information library.ServiceInitializationInformation) {
// Verify the secret // Verify the secret
var secret string var secret string
err = conn.QueryRow("SELECT secret FROM oauth WHERE appId = ?", r.Form.Get("client_id")).Scan(&secret) err = conn.DB.QueryRow("SELECT \"secret\" FROM oauth WHERE appId = $1", r.Form.Get("client_id")).Scan(&secret)
if err != nil { if err != nil {
renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information) renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information)
return return
@ -1013,7 +1113,7 @@ func Main(information library.ServiceInitializationInformation) {
var openIDTokenString string var openIDTokenString string
if openid { if openid {
var username string var username string
err := conn.QueryRow("SELECT username FROM users WHERE id = ?", userId).Scan(&username) err := conn.DB.QueryRow("SELECT username FROM users WHERE id = $1", userId).Scan(&username)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "29"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "29"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -1100,7 +1200,7 @@ func Main(information library.ServiceInitializationInformation) {
} }
// Delete the oauth entry // Delete the oauth entry
_, err = conn.Exec("DELETE FROM oauth WHERE appId = ? AND creator = ?", data.AppID, userId) _, err = conn.DB.Exec("DELETE FROM oauth WHERE appId = $1 AND creator = $2", data.AppID, userId)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information) renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information)
@ -1176,9 +1276,9 @@ func Main(information library.ServiceInitializationInformation) {
// Insert the oauth entry // Insert the oauth entry
if clientKeyShare { if clientKeyShare {
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES (?, ?, ?, ?, ?, ?, ?)", appId, secret, userId, data.Name, data.RedirectUri, scopes, data.KeyShareUri) _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ($1, $2, $3, $4, $5, $6, $7)", appId, secret, userId, data.Name, data.RedirectUri, scopes, data.KeyShareUri)
} else { } else {
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES (?, ?, ?, ?, ?, ?)", appId, secret, userId, data.Name, data.RedirectUri, scopes) _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES ($1, $2, $3, $4, $5, $6)", appId, secret, userId, data.Name, data.RedirectUri, scopes)
} }
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "37"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "37"}, information)
@ -1211,7 +1311,7 @@ func Main(information library.ServiceInitializationInformation) {
} }
// Get the apps // Get the apps
rows, err := conn.Query("SELECT appId, name, redirectUri, scopes, keyShareUri FROM oauth WHERE creator = ?", userId) rows, err := conn.DB.Query("SELECT appId, name, redirectUri, scopes, keyShareUri FROM oauth WHERE creator = $1", userId)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "38"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "38"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -1286,7 +1386,7 @@ func Main(information library.ServiceInitializationInformation) {
} }
// Delete the user // Delete the user
_, err = conn.Exec("DELETE FROM users WHERE id = ?", userId) _, err = conn.DB.Exec("DELETE FROM users WHERE id = $1", userId)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "42"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "42"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -1302,7 +1402,7 @@ func Main(information library.ServiceInitializationInformation) {
} }
// Delete the user's oauth entries // Delete the user's oauth entries
_, err = conn.Exec("DELETE FROM oauth WHERE creator = ?", userId) _, err = conn.DB.Exec("DELETE FROM oauth WHERE creator = ?", userId)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "44"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "44"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -1405,7 +1505,7 @@ func Main(information library.ServiceInitializationInformation) {
} }
// Delete the session // Delete the session
_, err = mem.Exec("DELETE FROM sessions WHERE id = ? AND session = ?", userId, data.Session) _, err = mem.Exec("DELETE FROM sessions WHERE id = $1 AND session = ?", userId, data.Session)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "49"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "49"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -1438,7 +1538,7 @@ func Main(information library.ServiceInitializationInformation) {
} }
// Get the users // Get the users
rows, err := conn.Query("SELECT id, username, created FROM users") rows, err := conn.DB.Query("SELECT id, username, created FROM users")
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "50"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "50"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
@ -1504,7 +1604,7 @@ func Main(information library.ServiceInitializationInformation) {
// Sleep for half an hour // Sleep for half an hour
time.Sleep(time.Minute * 30) time.Sleep(time.Minute * 30)
// Delete everything in the spent table past it's expiry date // Delete everything in the spent and challenge-response tables that has expired
affected, err := mem.Exec("DELETE FROM spent WHERE expires < ?", time.Now().Unix()) affected, err := mem.Exec("DELETE FROM spent WHERE expires < ?", time.Now().Unix())
if err != nil { if err != nil {
logFunc(err.Error(), 1, information) logFunc(err.Error(), 1, information)
@ -1513,7 +1613,17 @@ func Main(information library.ServiceInitializationInformation) {
if err != nil { if err != nil {
logFunc(err.Error(), 1, information) logFunc(err.Error(), 1, information)
} else { } else {
logFunc("Spent cleanup complete, deleted "+strconv.FormatInt(affectedCount, 10)+" entries", 0, information) affected, err := mem.Exec("DELETE FROM challengeResponse WHERE expires < ?", time.Now().Unix())
if err != nil {
logFunc(err.Error(), 1, information)
} else {
affectedCount2, err := affected.RowsAffected()
if err != nil {
logFunc(err.Error(), 1, information)
} else {
logFunc("Cleanup complete, deleted "+strconv.FormatInt(affectedCount+affectedCount2, 10)+" entries", 0, information)
}
}
} }
} }
} }
@ -1614,9 +1724,9 @@ func Main(information library.ServiceInitializationInformation) {
// Insert the oauth entry // Insert the oauth entry
if clientKeyShare { if clientKeyShare {
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES (?, ?, ?, ?, ?, ?, ?)", appId, secret, serviceIDBytes, message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri) _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ($1, $2, $3, $4, $5, $6, $7)", appId, secret, serviceIDBytes, message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri)
} else { } else {
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES (?, ?, ?, ?, ?, ?)", appId, secret, serviceIDBytes, message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes) _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES ($1, $2, $3, $4, $5, $6)", appId, secret, serviceIDBytes, message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes)
} }
if err != nil { if err != nil {
information.Outbox <- library.InterServiceMessage{ information.Outbox <- library.InterServiceMessage{

View File

@ -107,23 +107,12 @@ func main() {
var query url.Values var query url.Values
// Redirect to dashboard if client_id is not a URL parameter // Parse the url parameters using url.ParseQuery
if js.Global().Get("window").Get("location").Get("search").String() == "" { var err error
js.Global().Get("window").Get("location").Call("replace", "/dashboard") query, err = url.ParseQuery(strings.TrimPrefix(js.Global().Get("window").Get("location").Get("search").String(), "?"))
} else { if err != nil {
// Parse the url parameters using url.ParseQuery js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Error parsing URL query: "+err.Error())
var err error return
query, err = url.ParseQuery(strings.TrimPrefix(js.Global().Get("window").Get("location").Get("search").String(), "?"))
if err != nil {
js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Error parsing URL query: "+err.Error())
return
}
// Redirect to dashboard if client_id is not a URL parameter
if !query.Has("client_id") {
js.Global().Get("window").Get("location").Call("replace", "/dashboard")
return
}
} }
var statusBox = js.Global().Get("document").Call("getElementById", "statusBox") var statusBox = js.Global().Get("document").Call("getElementById", "statusBox")

View File

@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"crypto/ed25519"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -15,16 +16,14 @@ import (
var currentInputType = 0 var currentInputType = 0
func hashPassword(password string, salt []byte) string { func hashPassword(password string, salt []byte) []byte {
return base64.StdEncoding.EncodeToString( return argon2.IDKey(
argon2.IDKey( []byte(password),
[]byte(password), salt,
salt, 32,
32, 19264,
19264, 1,
1, 32,
32,
),
) )
} }
@ -112,7 +111,7 @@ func main() {
statusBox.Set("innerText", "Hashing password...") statusBox.Set("innerText", "Hashing password...")
fmt.Println("Hashing password...") fmt.Println("Hashing password...")
// Fetch the salt from the server // Fetch the challenge from the server
body, err := json.Marshal(map[string]interface{}{ body, err := json.Marshal(map[string]interface{}{
"username": username, "username": username,
}) })
@ -156,21 +155,17 @@ func main() {
} }
if response.StatusCode == 200 { if response.StatusCode == 200 {
// Decode the salt // Hash the password
salt, err := base64.StdEncoding.DecodeString(responseMap["salt"].(string)) hashedPassword := hashPassword(password, []byte(username))
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error decoding salt: "+err.Error())
return
}
hashedPassword := hashPassword(password, salt) // Sign the challenge
signature := ed25519.Sign(ed25519.NewKeyFromSeed(hashedPassword), []byte(responseMap["challenge"].(string)))
// Hashed password computed, contact server // Hashed password computed, contact server
statusBox.Set("innerText", "Contacting server...") statusBox.Set("innerText", "Contacting server...")
signupBody := map[string]interface{}{ signupBody := map[string]interface{}{
"username": username, "username": username,
"password": hashedPassword, "signature": base64.StdEncoding.EncodeToString(signature),
} }
// Marshal the body // Marshal the body
@ -181,7 +176,7 @@ func main() {
return return
} }
// Send the password to the server // Send the request
requestUri, err = url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/login") requestUri, err = url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/login")
if err != nil { if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton) showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
@ -219,7 +214,7 @@ func main() {
fmt.Println("Logged in!") fmt.Println("Logged in!")
statusBox.Set("innerText", "Setting up encryption keys...") statusBox.Set("innerText", "Setting up encryption keys...")
localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string)) localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
localStorage.Call("setItem", "DONOTSHARE-clientKey", hashPassword(password, []byte("fg-auth-client"))) localStorage.Call("setItem", "DONOTSHARE-clientKey", base64.StdEncoding.EncodeToString(hashPassword(password, []byte("fg-auth-client"))))
// Redirect to app // Redirect to app
statusBox.Set("innerText", "Welcome!") statusBox.Set("innerText", "Welcome!")

View File

@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"crypto/ed25519"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
@ -29,16 +30,14 @@ func showElements(show bool, elements ...js.Value) {
} }
} }
func hashPassword(password string, salt []byte) string { func hashPassword(password string, salt []byte) []byte {
return base64.StdEncoding.EncodeToString( return argon2.IDKey(
argon2.IDKey( []byte(password),
[]byte(password), salt,
salt, 32,
32, 19264,
19264, 1,
1, 32,
32,
),
) )
} }
@ -77,10 +76,12 @@ func main() {
usernameBox.Set("disabled", true) usernameBox.Set("disabled", true)
passwordBox.Set("disabled", true) passwordBox.Set("disabled", true)
signupButton.Set("disabled", true) signupButton.Set("disabled", true)
if localStorage.Call("getItem", "CONFIG-captchaStarted").IsNull() { if localStorage.Call("getItem", "DEBUG-customCaptcha").IsNull() {
captchaStatus.Set("innerText", "CAPTCHA not started - start CAPTCHA to signup.") if localStorage.Call("getItem", "CONFIG-captchaStarted").IsNull() {
} else { captchaStatus.Set("innerText", "CAPTCHA not started - start CAPTCHA to signup.")
captchaStatus.Set("innerText", "Captcha calculation paused.") } else {
captchaStatus.Set("innerText", "Captcha calculation paused.")
}
} }
var captcha string var captcha string
@ -114,24 +115,17 @@ func main() {
// PoW challenge computed, hash password // PoW challenge computed, hash password
statusBox.Set("innerText", "Hashing password...") statusBox.Set("innerText", "Hashing password...")
// Generate a random salt
salt := make([]byte, 32)
_, err := rand.Read(salt)
if err != nil {
showElements(true, inputContainer, signupButton, loginButton)
statusBox.Set("innerText", "Error generating salt: "+err.Error())
return
}
// Hash the password // Hash the password
hashedPassword := hashPassword(password, salt) hashedPassword := hashPassword(password, []byte(username))
// Create a keypair from the password
publicKey := ed25519.NewKeyFromSeed(hashedPassword).Public().(ed25519.PublicKey)
// Hashed password computed, contact server // Hashed password computed, contact server
statusBox.Set("innerText", "Contacting server...") statusBox.Set("innerText", "Contacting server...")
signupBody := map[string]interface{}{ signupBody := map[string]interface{}{
"username": username, "username": username,
"password": hashedPassword, "publicKey": base64.StdEncoding.EncodeToString(publicKey),
"salt": base64.StdEncoding.EncodeToString(salt),
"proofOfWork": captcha, "proofOfWork": captcha,
} }
@ -180,7 +174,7 @@ func main() {
// Signup successful // Signup successful
statusBox.Set("innerText", "Setting up encryption keys...") statusBox.Set("innerText", "Setting up encryption keys...")
localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string)) localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
localStorage.Call("setItem", "DONOTSHARE-clientKey", hashPassword(password, []byte("fg-auth-client"))) localStorage.Call("setItem", "DONOTSHARE-clientKey", base64.StdEncoding.EncodeToString(hashPassword(password, []byte("fg-auth-client"))))
// Redirect to app // Redirect to app
statusBox.Set("innerText", "Welcome!") statusBox.Set("innerText", "Welcome!")
@ -211,48 +205,58 @@ func main() {
captchaInProgress := false captchaInProgress := false
captchaButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} { captchaButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if !captchaInProgress { if localStorage.Call("getItem", "DEBUG-customCaptcha").IsNull() {
captchaInProgress = true if !captchaInProgress {
captchaButton.Set("innerText", "Pause") captchaInProgress = true
localStorage.Call("setItem", "CONFIG-captchaStarted", "true") captchaButton.Set("innerText", "Pause")
go func() { localStorage.Call("setItem", "CONFIG-captchaStarted", "true")
go func() { go func() {
time.Sleep(time.Minute * 5) go func() {
if captchaInProgress { time.Sleep(time.Minute * 5)
captchaStatus.Set("innerText", "Taking a long time? Try the desktop version.") if captchaInProgress {
captchaStatus.Set("innerText", "Taking a long time? Try the desktop version.")
}
}()
for {
if !captchaInProgress {
captchaStatus.Set("innerText", "Captcha calculation paused.")
captchaButton.Set("innerText", "Start")
break
} else {
captchaStatus.Set("innerText", "Calculating captcha... Stopping or refreshing will not lose progress.")
powParams, powResult, err := pow("fg-auth-signup")
if err != nil {
captchaStatus.Set("innerText", "Error calculating captcha: "+err.Error())
captchaInProgress = false
break
}
if powResult[:2] == "00" {
localStorage.Call("removeItem", "CONFIG-captchaStarted")
captcha = "2:" + powParams
captchaStatus.Set("innerText", "Captcha calculated!")
captchaButton.Set("disabled", true)
captchaButton.Set("innerText", "Start")
usernameBox.Set("disabled", false)
passwordBox.Set("disabled", false)
signupButton.Set("disabled", false)
captchaInProgress = false
break
}
time.Sleep(time.Millisecond)
}
} }
}() }()
for { } else {
if !captchaInProgress { captchaInProgress = false
captchaStatus.Set("innerText", "Captcha calculation paused.") }
captchaButton.Set("innerText", "Start")
break
} else {
captchaStatus.Set("innerText", "Calculating captcha... Stopping or refreshing will not lose progress.")
powParams, powResult, err := pow("fg-auth-signup")
if err != nil {
captchaStatus.Set("innerText", "Error calculating captcha: "+err.Error())
captchaInProgress = false
break
}
if powResult[:2] == "00" {
localStorage.Call("removeItem", "CONFIG-captchaStarted")
captcha = "2:" + powParams
captchaStatus.Set("innerText", "Captcha calculated!")
captchaButton.Set("disabled", true)
captchaButton.Set("innerText", "Start")
usernameBox.Set("disabled", false)
passwordBox.Set("disabled", false)
signupButton.Set("disabled", false)
captchaInProgress = false
break
}
time.Sleep(time.Millisecond)
}
}
}()
} else { } else {
captchaInProgress = false captcha = localStorage.Call("getItem", "DEBUG-customCaptcha").String()
captchaStatus.Set("innerText", "Captcha calculated!")
captchaButton.Set("disabled", true)
captchaButton.Set("innerText", "Start")
usernameBox.Set("disabled", false)
passwordBox.Set("disabled", false)
signupButton.Set("disabled", false)
} }
return nil return nil

View File

@ -41,16 +41,20 @@ var ServiceInformation = library.Service{
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000003"), ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000003"),
} }
var conn *sql.DB var conn library.Database
func getQuota(user uuid.UUID, information library.ServiceInitializationInformation) (int64, error) { func getQuota(user uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
// Get the user's quota from the database // Get the user's quota from the database
var quota int64 var quota int64
err := conn.QueryRow("SELECT quota FROM quotas WHERE id = $1", user).Scan(&quota) userBytes, err := user.MarshalBinary()
if err != nil {
return 0, err
}
err = conn.DB.QueryRow("SELECT quota FROM quotas WHERE id = $1", userBytes).Scan(&quota)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
// The user has no quota set, so we'll set it to the default quota // The user has no quota set, so we'll set it to the default quota
_, err = conn.Exec("INSERT INTO quotas (id, quota) VALUES ($1, $2)", user, int64(information.Configuration["defaultQuota"].(float64))) _, err = conn.DB.Exec("INSERT INTO quotas (id, quota) VALUES ($1, $2)", userBytes, int64(information.Configuration["defaultQuota"].(float64)))
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -397,11 +401,18 @@ func Main(information library.ServiceInitializationInformation) {
if response.MessageType == 2 { if response.MessageType == 2 {
// This is the connection information // This is the connection information
// Set up the database connection // Set up the database connection
conn = response.Message.(*sql.DB) conn = response.Message.(library.Database)
// Create the quotas table if it doesn't exist // Create the quotas table if it doesn't exist
_, err := conn.Exec("CREATE TABLE IF NOT EXISTS quotas (id UUID PRIMARY KEY, quota BIGINT)") if conn.DBType == library.Sqlite {
if err != nil { _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS quotas (id BLOB PRIMARY KEY, quota BIGINT)")
logFunc(err.Error(), 3, information) if err != nil {
logFunc(err.Error(), 3, information)
}
} else {
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS quotas (id BYTEA PRIMARY KEY, quota BIGINT)")
if err != nil {
logFunc(err.Error(), 3, information)
}
} }
} else { } else {
// This is an error message // This is an error message