diff --git a/go.mod b/go.mod
index 542cf33..d20a4eb 100644
--- a/go.mod
+++ b/go.mod
@@ -3,9 +3,9 @@ module git.ailur.dev/ailur/fulgens
go 1.23.0
require (
- git.ailur.dev/ailur/pow-argon2 v0.0.0-20240922143345-8f9af6dce3a5
+ git.ailur.dev/ailur/pow v0.0.0-20240929101731-4d0b2593b7dd
github.com/cespare/xxhash/v2 v2.3.0
- github.com/go-chi/chi v1.5.5
+ github.com/go-chi/chi/v5 v5.1.0
github.com/go-playground/validator/v10 v10.22.1
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.6.0
diff --git a/go.sum b/go.sum
index 2f6e606..9ffe6ef 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-git.ailur.dev/ailur/pow-argon2 v0.0.0-20240922143345-8f9af6dce3a5 h1:QGICG5QsXtGVDV0YjR4bXiEV2kWQ96riPR9qFWuFpr4=
-git.ailur.dev/ailur/pow-argon2 v0.0.0-20240922143345-8f9af6dce3a5/go.mod h1:dDulL+Bfr47BPmvSPRkRD3uOVNsfM6yOTwhdAkctZU4=
+git.ailur.dev/ailur/pow v0.0.0-20240929101731-4d0b2593b7dd h1:yJRi9yGRICOb6NSIE9dBRbHsWU+jSUEeAFohVW59n38=
+git.ailur.dev/ailur/pow v0.0.0-20240929101731-4d0b2593b7dd/go.mod h1:BHl7H6B6uN+q2cCCUlno6JMhqLa2A52wkbAdJbq2izA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -10,6 +10,8 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
+github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
+github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
diff --git a/library/main.go b/library/main.go
index 8610c00..b11a44e 100644
--- a/library/main.go
+++ b/library/main.go
@@ -1,7 +1,7 @@
package library
import (
- "github.com/go-chi/chi"
+ "github.com/go-chi/chi/v5"
"github.com/google/uuid"
"io/fs"
"time"
diff --git a/main.go b/main.go
index 519b6b0..ea582d6 100644
--- a/main.go
+++ b/main.go
@@ -18,7 +18,7 @@ import (
"net/http"
"path/filepath"
- "github.com/go-chi/chi"
+ "github.com/go-chi/chi/v5"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
diff --git a/services-src/auth/main.go b/services-src/auth/main.go
index 9b6b176..3e27b21 100644
--- a/services-src/auth/main.go
+++ b/services-src/auth/main.go
@@ -4,9 +4,7 @@ import (
// Fulgens libraries
"git.ailur.dev/ailur/fulgens/library"
authLibrary "git.ailur.dev/ailur/fulgens/services-src/auth/library"
-
- // First-party libraries
- pow "git.ailur.dev/ailur/pow-argon2/library"
+ "git.ailur.dev/ailur/pow"
// Standard libraries
"bytes"
@@ -417,9 +415,58 @@ func Main(information library.ServiceInitializationInformation) {
logFunc(err.Error(), 1, information)
}
}(r.Body)
- renderTemplate(200, w, map[string]interface{}{
- "identifier": identifier,
- }, "clientKeyShare.html", information)
+ // Parse the JWT from the query string
+ if r.URL.Query().Get("token") == "" {
+ renderString(400, w, "No token provided", information)
+ return
+ }
+
+ // Verify the JWT
+ _, claims, ok := verifyJwt(strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer "), publicKey, mem)
+ if !ok {
+ renderString(401, w, "Invalid token", information)
+ return
+ }
+
+ // Check if they have the clientKeyShare scope
+ var scopes string
+ err = conn.QueryRow("SELECT scopes FROM oauth WHERE appId = ?", claims["aud"]).Scan(&scopes)
+ if err != nil {
+ renderString(500, w, "Sorry, something went wrong on our end. Error code: 20. Please report to the administrator.", information)
+ logFunc(err.Error(), 2, information)
+ return
+ }
+
+ // Unmarshal the scopes
+ var scopesArray []string
+ err = json.Unmarshal([]byte(scopes), &scopesArray)
+ if err != nil {
+ renderString(500, w, "Sorry, something went wrong on our end. Error code: 21. Please report to the administrator.", information)
+ logFunc(err.Error(), 2, information)
+ return
+ }
+
+ // Check if the clientKeyShare scope is present
+ var hasClientKeyShare bool
+ for _, scope := range scopesArray {
+ if scope == "clientKeyShare" {
+ hasClientKeyShare = true
+ break
+ }
+ }
+ if !hasClientKeyShare {
+ renderString(403, w, "Missing scope", information)
+ return
+ }
+
+ // Check it's not an openid token
+ if claims["isOpenID"] == true {
+ renderString(400, w, "Invalid token", information)
+ } else {
+ renderTemplate(200, w, map[string]interface{}{
+ "identifier": identifier,
+ }, "clientKeyShare.html", information)
+ }
})
router.Get("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
@@ -520,24 +567,9 @@ func Main(information library.ServiceInitializationInformation) {
return
}
- // Check if the PoW is spent
- hash := make([]byte, 8)
- binary.LittleEndian.PutUint64(hash, xxhash.Sum64String(data.ProofOfWork))
- _, err = mem.Exec("INSERT INTO spent (hash, expires) VALUES (?, ?)", hash, time.Now().Unix()+60)
- if err != nil {
- if strings.Contains(err.Error(), "UNIQUE constraint failed") {
- renderJSON(409, w, map[string]interface{}{"error": "Proof of work already spent"}, information)
- return
- } else {
- renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "07"}, information)
- logFunc(err.Error(), 2, information)
- return
- }
- }
-
// Check if the difficulty, timestamp and resource are correct
powSlice := strings.Split(data.ProofOfWork, ":")
- if powSlice[0] != "3" || powSlice[3] != "fg-auth-signup" {
+ if powSlice[0] != "2" || powSlice[3] != "fg-auth-signup" {
renderJSON(400, w, map[string]interface{}{"error": "Invalid PoW"}, information)
return
}
@@ -554,11 +586,26 @@ func Main(information library.ServiceInitializationInformation) {
}
// Verify the PoW
- if !pow.VerifyPoW(data.ProofOfWork) {
+ if !ailur_pow.VerifyPoW(data.ProofOfWork) {
renderJSON(400, w, map[string]interface{}{"error": "Invalid PoW"}, information)
return
}
+ // Check if the PoW is spent
+ hash := make([]byte, 8)
+ binary.LittleEndian.PutUint64(hash, xxhash.Sum64String(data.ProofOfWork))
+ _, err = mem.Exec("INSERT INTO spent (hash, expires) VALUES (?, ?)", hash, time.Now().Unix()+60)
+ if err != nil {
+ if strings.Contains(err.Error(), "UNIQUE constraint failed") {
+ renderJSON(400, w, map[string]interface{}{"error": "Proof of work already spent"}, information)
+ return
+ } else {
+ renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "07"}, information)
+ logFunc(err.Error(), 2, information)
+ return
+ }
+ }
+
// Decode the password
password, err := base64.StdEncoding.DecodeString(data.Password)
if err != nil {
@@ -858,65 +905,6 @@ func Main(information library.ServiceInitializationInformation) {
renderJSON(200, w, map[string]interface{}{"username": username, "sub": uuid.Must(uuid.FromBytes(userId)).String()}, information)
})
- router.Get("/api/oauth/clientKeyShare", func(w http.ResponseWriter, r *http.Request) {
- defer func(Body io.ReadCloser) {
- err := Body.Close()
- if err != nil {
- logFunc(err.Error(), 1, information)
- }
- }(r.Body)
- // Parse the JWT
- if r.Header.Get("Authorization") == "" {
- renderJSON(401, w, map[string]interface{}{"error": "No token provided"}, information)
- return
- }
-
- // Verify the JWT
- _, claims, ok := verifyJwt(strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer "), publicKey, mem)
- if !ok {
- renderJSON(401, w, map[string]interface{}{"error": "Invalid token"}, information)
- return
- }
-
- // Check if they have the clientKeyShare scope
- var scopes string
- err = conn.QueryRow("SELECT scopes FROM oauth WHERE appId = ?", claims["aud"]).Scan(&scopes)
- if err != nil {
- renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "20"}, information)
- logFunc(err.Error(), 2, information)
- return
- }
-
- // Unmarshal the scopes
- var scopesArray []string
- err = json.Unmarshal([]byte(scopes), &scopesArray)
- if err != nil {
- renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "21"}, information)
- logFunc(err.Error(), 2, information)
- return
- }
-
- // Check if the clientKeyShare scope is present
- var hasClientKeyShare bool
- for _, scope := range scopesArray {
- if scope == "clientKeyShare" {
- hasClientKeyShare = true
- break
- }
- }
- if !hasClientKeyShare {
- renderJSON(403, w, map[string]interface{}{"error": "Missing scope"}, information)
- return
- }
-
- // Check it's not an openid token
- if claims["isOpenID"] == true {
- renderJSON(401, w, map[string]interface{}{"error": "Invalid token"}, information)
- } else {
- renderJSON(200, w, map[string]interface{}{"key": publicKey}, information)
- }
- })
-
router.Post("/api/authorize", func(w http.ResponseWriter, r *http.Request) {
defer func(Body io.ReadCloser) {
err := Body.Close()
diff --git a/services-src/auth/resources/go.mod b/services-src/auth/resources/go.mod
index 0f68e65..151e7d5 100644
--- a/services-src/auth/resources/go.mod
+++ b/services-src/auth/resources/go.mod
@@ -3,7 +3,6 @@ module git.ailur.dev/fulgens/services-src/auth/resources-src
go 1.23.0
require (
- git.ailur.dev/ailur/pow-argon2 v0.0.0-20240922143345-8f9af6dce3a5
github.com/cespare/xxhash/v2 v2.3.0
golang.org/x/crypto v0.27.0
)
diff --git a/services-src/auth/resources/go.sum b/services-src/auth/resources/go.sum
index e90db0e..ff348bf 100644
--- a/services-src/auth/resources/go.sum
+++ b/services-src/auth/resources/go.sum
@@ -1,5 +1,5 @@
-git.ailur.dev/ailur/pow-argon2 v0.0.0-20240922143345-8f9af6dce3a5 h1:QGICG5QsXtGVDV0YjR4bXiEV2kWQ96riPR9qFWuFpr4=
-git.ailur.dev/ailur/pow-argon2 v0.0.0-20240922143345-8f9af6dce3a5/go.mod h1:dDulL+Bfr47BPmvSPRkRD3uOVNsfM6yOTwhdAkctZU4=
+git.ailur.dev/ailur/pow v0.0.0-20240929101731-4d0b2593b7dd h1:yJRi9yGRICOb6NSIE9dBRbHsWU+jSUEeAFohVW59n38=
+git.ailur.dev/ailur/pow v0.0.0-20240929101731-4d0b2593b7dd/go.mod h1:BHl7H6B6uN+q2cCCUlno6JMhqLa2A52wkbAdJbq2izA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
diff --git a/services-src/auth/resources/static/css/style.css b/services-src/auth/resources/static/css/style.css
index fd6ee05..32ef8ec 100644
--- a/services-src/auth/resources/static/css/style.css
+++ b/services-src/auth/resources/static/css/style.css
@@ -11,6 +11,9 @@
--hover-nonimportant-theme-color: #dbdbdb;
--nonimportant-text-color: #000;
--inOutDiv: #fafafa;
+ --disabled: lightgray;
+ --disabled-hover: #a2a0a0;
+ --disabled-text-color: gray;
}
/* dark mode */
@@ -21,8 +24,11 @@
--inOutDiv: #2d2f31;
--text-color: #ffffff;
--editor: #1E1E1E;
+ --nonimporant-theme-color: #8E8E8E;
--nonimportant-text-color: #fff;
--border-color: #393b3d;
+ --disabled: #606060;
+ --disabled-hover: #737373;
}
.inOutDiv p {
@@ -46,6 +52,8 @@ h2,
h3,
h4,
h5,
+span,
+text,
h6 {
color: var(--text-color);
white-space: break-spaces;
@@ -90,11 +98,43 @@ input {
min-width: 20px;
}
+.inputBox .captchaDiv {
+ background-color: var(--editor);
+ height: 32px;
+ width: calc(100% - 15px);
+ margin: 0 5px 0 5px;
+ border-radius: 8px;
+ border: 1px var(--border-color) solid;
+ display: flex;
+}
+
+.inputBox .captchaDiv button {
+ background-color: var(--editor);
+ color: var(--text-color);
+ border-right: 1px solid var(--border-color);
+ border-radius: 0;
+ padding: 0 10px 0 0;
+ margin: 0 0 0 10px;
+}
+
+.inputBox .captchaDiv .vAlign {
+ margin-left: 5px;
+}
+
+.inputBox .captchaDiv .vAlign span {
+ font-size: 14px;
+}
+
.inputBox input {
margin-left: 5px;
margin-right: 0;
}
+.inputBox input:disabled {
+ background-color: var(--disabled);
+ color: var(--disabled-text-color);
+}
+
@media only screen and (max-width: 600px) {
body {
background-color: var(--inOutDiv);
@@ -210,6 +250,15 @@ button {
transition: 0.125s;
}
+button:disabled {
+ background-color: var(--disabled);
+ color: var(--disabled-text-color);
+}
+
+button:disabled:hover {
+ background-color: var(--disabled-hover);
+}
+
button:hover {
background-color: var(--hover-theme-color);
transition: all 0.3s ease 0s;
diff --git a/services-src/auth/resources/static/svg/gopher.svg b/services-src/auth/resources/static/svg/gopher.svg
deleted file mode 100644
index d6451bf..0000000
--- a/services-src/auth/resources/static/svg/gopher.svg
+++ /dev/null
@@ -1,174 +0,0 @@
-
-
-
-
diff --git a/services-src/auth/resources/templates/signup.html b/services-src/auth/resources/templates/signup.html
index b37f57e..ee08e71 100644
--- a/services-src/auth/resources/templates/signup.html
+++ b/services-src/auth/resources/templates/signup.html
@@ -18,16 +18,27 @@
-
+
Privacy & Terms
diff --git a/services-src/auth/resources/wasm/clientKeyShare/main.go b/services-src/auth/resources/wasm/clientKeyShare/main.go
index 64e72a4..49f0165 100644
--- a/services-src/auth/resources/wasm/clientKeyShare/main.go
+++ b/services-src/auth/resources/wasm/clientKeyShare/main.go
@@ -1,15 +1,11 @@
package main
import (
- "bytes"
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
- "encoding/json"
- "fmt"
- "net/http"
"net/url"
"strings"
"syscall/js"
@@ -24,161 +20,71 @@ func main() {
statusBox := js.Global().Get("document").Call("getElementById", "statusBox")
- // Check if the token is valid
- requestUri, err := url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/loggedIn")
- if err != nil {
- statusBox.Set("innerText", "Error joining URL: "+err.Error())
- return
- }
-
- loggedInBody := map[string]interface{}{
- "token": localStorage.Call("getItem", "DONOTSHARE-secretKey").String(),
- }
-
- // Marshal the body
- body, err := json.Marshal(loggedInBody)
- if err != nil {
- statusBox.Set("innerText", "Error marshaling signup body: "+err.Error())
- return
- }
-
- response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
- if err != nil {
- statusBox.Set("innerText", "Error contacting server: "+err.Error())
- return
- }
-
- // Check if the response is 200
- if response.StatusCode == 401 {
- // Close the response body
- err = response.Body.Close()
- if err != nil {
- fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
- }
-
- // Redirect to log-out if not signed in
- js.Global().Get("window").Get("location").Call("replace", "/logout"+js.Global().Get("window").Get("location").Get("search").String())
- return
- } else if response.StatusCode == 500 {
- // Read the response
- var responseMap map[string]interface{}
- decoder := json.NewDecoder(response.Body)
- err = decoder.Decode(&responseMap)
- if err != nil {
- js.Global().Call("alert", "Error decoding server response: "+err.Error())
- return
- }
-
- // Close the response body
- err = response.Body.Close()
- if err != nil {
- fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
- }
-
- // Alert the user if the server is down
- js.Global().Call("alert", "Something went wrong! (error code: "+responseMap["code"].(string)+")")
- return
- }
-
- // Close the response body
- err = response.Body.Close()
- if err != nil {
- fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
- }
-
+ // Parse the query string
query, err := url.ParseQuery(strings.TrimPrefix(js.Global().Get("window").Get("location").Get("search").String(), "?"))
if err != nil {
statusBox.Set("innerText", "Error parsing query: "+err.Error())
return
}
- // Check if the access token we were given is valid and that the scope is correct
- requestUri, err = url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/oauth/clientKeyShare")
+ // Get the ECDH public key from the query string
+ clientKeyBytes, err := base64.URLEncoding.DecodeString(query.Get("ecdhPublicKey"))
if err != nil {
- statusBox.Set("innerText", "Error joining URL: "+err.Error())
+ statusBox.Set("innerText", "Error decoding ECDH public key: "+err.Error())
return
}
- request, err := http.NewRequest("GET", requestUri, nil)
+ // Encode the ECDH public key
+ key, err := ecdh.X25519().NewPublicKey(clientKeyBytes)
if err != nil {
- statusBox.Set("innerText", "Error creating request: "+err.Error())
+ statusBox.Set("innerText", "Error encoding ECDH public key: "+err.Error())
return
}
- request.Header.Set("Authorization", "Bearer "+query.Get("accessToken"))
-
- response, err = http.DefaultClient.Do(request)
+ // Generate a new ECDH key pair
+ privateKey, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
- statusBox.Set("innerText", "Error contacting server: "+err.Error())
+ statusBox.Set("innerText", "Error generating ECDH key pair: "+err.Error())
return
}
- // Close the response body
- err = response.Body.Close()
+ // Generate the shared secret
+ sharedSecret, err := privateKey.ECDH(key)
if err != nil {
- fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
+ statusBox.Set("innerText", "Error generating shared secret: "+err.Error())
+ return
}
- if response.StatusCode == 200 {
- // Get the ECDH public key from the query string
- clientKeyBytes, err := base64.URLEncoding.DecodeString(query.Get("ecdhPublicKey"))
- if err != nil {
- statusBox.Set("innerText", "Error decoding ECDH public key: "+err.Error())
- return
- }
-
- // Encode the ECDH public key
- key, err := ecdh.X25519().NewPublicKey(clientKeyBytes)
- if err != nil {
- statusBox.Set("innerText", "Error encoding ECDH public key: "+err.Error())
- return
- }
-
- // Generate a new ECDH key pair
- privateKey, err := ecdh.X25519().GenerateKey(rand.Reader)
- if err != nil {
- statusBox.Set("innerText", "Error generating ECDH key pair: "+err.Error())
- return
- }
-
- // Generate the shared secret
- sharedSecret, err := privateKey.ECDH(key)
- if err != nil {
- statusBox.Set("innerText", "Error generating shared secret: "+err.Error())
- return
- }
-
- // AES-GCM encrypt the DONOTSHARE-clientKey
- block, err := aes.NewCipher(sharedSecret)
- if err != nil {
- statusBox.Set("innerText", "Error creating AES cipher: "+err.Error())
- return
- }
-
- gcm, err := cipher.NewGCM(block)
- if err != nil {
- statusBox.Set("innerText", "Error creating GCM cipher: "+err.Error())
- return
- }
-
- nonce := make([]byte, gcm.NonceSize())
- _, err = rand.Read(nonce)
- if err != nil {
- statusBox.Set("innerText", "Error generating nonce: "+err.Error())
- return
- }
-
- // Un-base64 the client key
- decodedClientKey, err := base64.StdEncoding.DecodeString(localStorage.Call("getItem", "DONOTSHARE-clientKey").String())
- if err != nil {
- statusBox.Set("innerText", "Error decoding client key: "+err.Error())
- return
- }
-
- encryptedClientKey := gcm.Seal(nil, nonce, decodedClientKey, nil)
-
- // Redirect back to the referrer with the encrypted client key
- redirectUri := strings.Split(js.Global().Get("document").Get("referrer").String(), "?")[0]
- js.Global().Get("window").Get("location").Call("replace", redirectUri+"?ecdhPublicKey="+base64.URLEncoding.EncodeToString(privateKey.PublicKey().Bytes())+"&nonce="+base64.URLEncoding.EncodeToString(nonce)+"&cipherText="+base64.URLEncoding.EncodeToString(encryptedClientKey))
+ // AES-GCM encrypt the DONOTSHARE-clientKey
+ block, err := aes.NewCipher(sharedSecret)
+ if err != nil {
+ statusBox.Set("innerText", "Error creating AES cipher: "+err.Error())
+ return
}
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ statusBox.Set("innerText", "Error creating GCM cipher: "+err.Error())
+ return
+ }
+
+ nonce := make([]byte, gcm.NonceSize())
+ _, err = rand.Read(nonce)
+ if err != nil {
+ statusBox.Set("innerText", "Error generating nonce: "+err.Error())
+ return
+ }
+
+ // Un-base64 the client key
+ decodedClientKey, err := base64.StdEncoding.DecodeString(localStorage.Call("getItem", "DONOTSHARE-clientKey").String())
+ if err != nil {
+ statusBox.Set("innerText", "Error decoding client key: "+err.Error())
+ return
+ }
+
+ encryptedClientKey := gcm.Seal(nil, nonce, decodedClientKey, nil)
+
+ // Redirect back to the referrer with the encrypted client key
+ redirectUri := strings.Split(js.Global().Get("document").Get("referrer").String(), "?")[0]
+ js.Global().Get("window").Get("location").Call("replace", redirectUri+"?ecdhPublicKey="+base64.URLEncoding.EncodeToString(privateKey.PublicKey().Bytes())+"&nonce="+base64.URLEncoding.EncodeToString(nonce)+"&cipherText="+base64.URLEncoding.EncodeToString(encryptedClientKey))
}
diff --git a/services-src/auth/resources/wasm/login/main.go b/services-src/auth/resources/wasm/login/main.go
index 27c4dab..dba31c6 100644
--- a/services-src/auth/resources/wasm/login/main.go
+++ b/services-src/auth/resources/wasm/login/main.go
@@ -155,81 +155,89 @@ func main() {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
- // Decode the salt
- salt, err := base64.StdEncoding.DecodeString(responseMap["salt"].(string))
- 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)
-
- // Hashed password computed, contact server
- statusBox.Set("innerText", "Contacting server...")
- signupBody := map[string]interface{}{
- "username": username,
- "password": hashedPassword,
- }
-
- // Marshal the body
- body, err = json.Marshal(signupBody)
- if err != nil {
- showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
- statusBox.Set("innerText", "Error marshaling signup body: "+err.Error())
- return
- }
-
- // Send the password to the server
- requestUri, err = url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/login")
- if err != nil {
- showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
- statusBox.Set("innerText", "Error joining URL: "+err.Error())
- return
- }
-
- // Send the request
- fmt.Println("Sending request to", requestUri)
- response, err = http.Post(requestUri, "application/json", bytes.NewReader(body))
- if err != nil {
- showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
- statusBox.Set("innerText", "Error contacting server: "+err.Error())
- return
- }
-
- // Read the response
- fmt.Println("Reading response...")
- decoder = json.NewDecoder(response.Body)
- err = decoder.Decode(&responseMap)
- if err != nil {
- showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
- statusBox.Set("innerText", "Error decoding server response: "+err.Error())
- return
- }
-
- // Close the response body
- err = response.Body.Close()
- if err != nil {
- fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
- }
-
if response.StatusCode == 200 {
- // Logged in
- fmt.Println("Logged in!")
- statusBox.Set("innerText", "Setting up encryption keys...")
- localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
- localStorage.Call("setItem", "DONOTSHARE-clientKey", hashPassword(password, []byte("fg-auth-client")))
+ // Decode the salt
+ salt, err := base64.StdEncoding.DecodeString(responseMap["salt"].(string))
+ if err != nil {
+ showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
+ statusBox.Set("innerText", "Error decoding salt: "+err.Error())
+ return
+ }
- // Redirect to app
- statusBox.Set("innerText", "Welcome!")
- time.Sleep(time.Second)
- js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
- } else if response.StatusCode == 401 {
- // Login failed
+ hashedPassword := hashPassword(password, salt)
+
+ // Hashed password computed, contact server
+ statusBox.Set("innerText", "Contacting server...")
+ signupBody := map[string]interface{}{
+ "username": username,
+ "password": hashedPassword,
+ }
+
+ // Marshal the body
+ body, err = json.Marshal(signupBody)
+ if err != nil {
+ showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
+ statusBox.Set("innerText", "Error marshaling signup body: "+err.Error())
+ return
+ }
+
+ // Send the password to the server
+ requestUri, err = url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/login")
+ if err != nil {
+ showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
+ statusBox.Set("innerText", "Error joining URL: "+err.Error())
+ return
+ }
+
+ // Send the request
+ fmt.Println("Sending request to", requestUri)
+ response, err = http.Post(requestUri, "application/json", bytes.NewReader(body))
+ if err != nil {
+ showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
+ statusBox.Set("innerText", "Error contacting server: "+err.Error())
+ return
+ }
+
+ // Read the response
+ fmt.Println("Reading response...")
+ decoder = json.NewDecoder(response.Body)
+ err = decoder.Decode(&responseMap)
+ if err != nil {
+ showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
+ statusBox.Set("innerText", "Error decoding server response: "+err.Error())
+ return
+ }
+
+ // Close the response body
+ err = response.Body.Close()
+ if err != nil {
+ fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
+ }
+
+ if response.StatusCode == 200 {
+ // Logged in
+ fmt.Println("Logged in!")
+ statusBox.Set("innerText", "Setting up encryption keys...")
+ localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
+ localStorage.Call("setItem", "DONOTSHARE-clientKey", hashPassword(password, []byte("fg-auth-client")))
+
+ // Redirect to app
+ statusBox.Set("innerText", "Welcome!")
+ time.Sleep(time.Second)
+ js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
+ } else if response.StatusCode == 401 {
+ // Login failed
+ showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
+ statusBox.Set("innerText", "Username or password incorrect!")
+ } else {
+ // Unknown error
+ showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
+ statusBox.Set("innerText", "Something went wrong! (error code: "+responseMap["code"].(string)+")")
+ }
+ } else if response.StatusCode != 500 {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
- statusBox.Set("innerText", "Username or password incorrect!")
+ statusBox.Set("innerText", responseMap["error"].(string))
} else {
- // Unknown error
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Something went wrong! (error code: "+responseMap["code"].(string)+")")
}
diff --git a/services-src/auth/resources/wasm/signup/main.go b/services-src/auth/resources/wasm/signup/main.go
index 04cf7ec..7d9ef47 100644
--- a/services-src/auth/resources/wasm/signup/main.go
+++ b/services-src/auth/resources/wasm/signup/main.go
@@ -4,14 +4,16 @@ import (
"bytes"
"crypto/rand"
"encoding/base64"
+ "encoding/binary"
+ "encoding/hex"
"fmt"
+ "strconv"
"time"
"encoding/json"
"net/http"
"net/url"
- "git.ailur.dev/ailur/pow-argon2/library"
"golang.org/x/crypto/argon2"
"syscall/js"
@@ -40,6 +42,21 @@ func hashPassword(password string, salt []byte) string {
)
}
+// This is my code: I can re-license it I all like, despite it being MIT licensed
+func pow(resource string) (string, string, error) {
+ initialTime := time.Now().Unix()
+ var timestamp [8]byte
+ binary.LittleEndian.PutUint64(timestamp[:], uint64(initialTime))
+
+ var nonce [16]byte
+ _, err := rand.Read(nonce[:])
+ if err != nil {
+ return "", "", err
+ }
+
+ return strconv.FormatInt(initialTime, 10) + ":" + hex.EncodeToString(nonce[:]) + ":" + resource + ":", hex.EncodeToString(argon2.IDKey(nonce[:], bytes.Join([][]byte{timestamp[:], []byte(resource)}, []byte{}), 1, 64*1024, 4, 32)), nil
+}
+
func main() {
// Redirect to app if already signed in
localStorage := js.Global().Get("localStorage")
@@ -53,6 +70,20 @@ func main() {
var signupButton = js.Global().Get("document").Call("getElementById", "signupButton")
var loginButton = js.Global().Get("document").Call("getElementById", "loginButton")
var inputContainer = js.Global().Get("document").Call("getElementById", "inputContainer")
+ var captchaButton = js.Global().Get("document").Call("getElementById", "captchaButton")
+ var captchaStatus = js.Global().Get("document").Call("getElementById", "captchaStatus")
+
+ captchaButton.Set("disabled", false)
+ usernameBox.Set("disabled", true)
+ passwordBox.Set("disabled", true)
+ signupButton.Set("disabled", true)
+ if localStorage.Call("getItem", "CONFIG-captchaStarted").IsNull() {
+ captchaStatus.Set("innerText", "CAPTCHA not started - start CAPTCHA to signup.")
+ } else {
+ captchaStatus.Set("innerText", "Captcha calculation paused.")
+ }
+
+ var captcha string
signupButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go func() {
@@ -76,20 +107,8 @@ func main() {
// Start the signup process
fmt.Println("Starting signup process for user: " + username)
showElements(false, inputContainer, signupButton, loginButton)
- // Wait about 10ms to allow the UI to update
- var pow string
- if localStorage.Call("getItem", "DEBUG-customPoW").IsNull() {
- var err error
- statusBox.Set("innerText", "Computing PoW Challenge...\nThe UI may be unresponsive during this time, as we are performing a lot of work! Please wait a few minutes for the process to complete.")
- time.Sleep(time.Millisecond * 10)
- pow, err = library.PoW(3, "fg-auth-signup")
- if err != nil {
- showElements(true, inputContainer, signupButton, loginButton)
- statusBox.Set("innerText", "Error computing PoW challenge: "+err.Error())
- return
- }
- } else {
- pow = localStorage.Call("getItem", "DEBUG-customPoW").String()
+ if captcha == "" {
+ statusBox.Set("innerText", "You must have a valid captcha! Press the \"Start\" button to start calculating a captcha.")
}
// PoW challenge computed, hash password
@@ -113,7 +132,7 @@ func main() {
"username": username,
"password": hashedPassword,
"salt": base64.StdEncoding.EncodeToString(salt),
- "proofOfWork": pow,
+ "proofOfWork": captcha,
}
// Marshal the body
@@ -170,7 +189,7 @@ func main() {
} else if response.StatusCode == 409 {
// Username taken
showElements(true, inputContainer, signupButton, loginButton)
- statusBox.Set("innerText", "Username or password taken!")
+ statusBox.Set("innerText", "Username already taken!")
} else if response.StatusCode != 500 {
// Other error
showElements(true, inputContainer, signupButton, loginButton)
@@ -190,6 +209,55 @@ func main() {
return nil
}))
+ captchaInProgress := false
+ captchaButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+ if !captchaInProgress {
+ captchaInProgress = true
+ captchaButton.Set("innerText", "Pause")
+ localStorage.Call("setItem", "CONFIG-captchaStarted", "true")
+ go func() {
+ go func() {
+ time.Sleep(time.Minute * 5)
+ 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)
+ }
+ }
+ }()
+ } else {
+ captchaInProgress = false
+ }
+
+ return nil
+ }))
+
// Wait for events
select {}
}