diff --git a/.env.development b/.env.development index 5b9b925..bc93d64 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,4 @@ VITE_SSO_ORIGIN=http://localhost:9090 VITE_API_LOTUS=http://localhost:9095/v1/lotus +VITE_IMAP_LOTUS=ws://localhost:9095/v1/lotus/imap diff --git a/.env.production b/.env.production index 7a9113c..b45caba 100644 --- a/.env.production +++ b/.env.production @@ -1,3 +1,4 @@ VITE_SSO_ORIGIN=https://sso.1f349.com VITE_API_LOTUS=https://api.1f349.com/v1/lotus +VITE_IMAP_LOTUS=wss://api.1f349.com/v1/lotus/imap diff --git a/src/App.svelte b/src/App.svelte index 2839fc4..8b6dce6 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,20 +1,23 @@
@@ -77,12 +179,18 @@ {:else}
Warning: This is currently still under development
+
+ +
{JSON.stringify(folders, null, 2)}
+
+
{/if} diff --git a/src/types/imap.ts b/src/types/imap.ts new file mode 100644 index 0000000..bd23cff --- /dev/null +++ b/src/types/imap.ts @@ -0,0 +1,5 @@ +export interface ImapFolder { + Attributes: string[]; + Delimiter: string; + Name: string; +} diff --git a/src/types/internal.ts b/src/types/internal.ts new file mode 100644 index 0000000..3395d76 --- /dev/null +++ b/src/types/internal.ts @@ -0,0 +1,9 @@ +export interface TreeFolder { + name: string; + attr: Set; + children: TreeFolder[]; +} + +export interface RootFolder extends TreeFolder { + role: string; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index d9ca7f2..3f3912f 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -4,6 +4,7 @@ interface ImportMetaEnv { VITE_SSO_ORIGIN: string; VITE_API_LOTUS: string; + VITE_IMAP_LOTUS: string; } interface ImportMeta { diff --git a/test-server/go.mod b/test-server/go.mod index d96580e..3db34ff 100644 --- a/test-server/go.mod +++ b/test-server/go.mod @@ -6,11 +6,13 @@ require ( github.com/1f349/mjwt v0.2.0 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/uuid v1.4.0 + github.com/gorilla/websocket v1.5.1 github.com/rs/cors v1.10.1 ) require ( github.com/becheran/wildmatch-go v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect + golang.org/x/net v0.17.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/test-server/go.sum b/test-server/go.sum index b1f2f87..cedf882 100644 --- a/test-server/go.sum +++ b/test-server/go.sum @@ -8,6 +8,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -16,6 +18,8 @@ github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/test-server/main.go b/test-server/main.go index 4b56ff2..b046cf3 100644 --- a/test-server/main.go +++ b/test-server/main.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "net/http" + "slices" "strings" "time" @@ -14,9 +15,16 @@ import ( "github.com/1f349/mjwt/claims" "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" + "github.com/gorilla/websocket" "github.com/rs/cors" ) +var wsUpgrade = &websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + func main() { log.Println("Starting test server") signer, err := mjwt.NewMJwtSignerFromFileOrCreate("Test SSO Service", "private.key.local", rand.Reader, 2048) @@ -34,7 +42,7 @@ func ssoServer(signer mjwt.Signer) { r := http.NewServeMux() r.HandleFunc("/popup", func(w http.ResponseWriter, r *http.Request) { ps := claims.NewPermStorage() - ps.Set("mail-client") + ps.Set("mail:inbox=admin@localhost") accessToken, err := signer.GenerateJwt("81b99bd7-bf74-4cc2-9133-80ed2393dfe6", uuid.NewString(), jwt.ClaimStrings{"d0555671-df9d-42d0-a4d6-94b694251f0b"}, 15*time.Minute, auth.AccessTokenClaims{ Perms: ps, }) @@ -123,13 +131,73 @@ func apiServer(verify mjwt.Verifier) { } json.NewEncoder(rw).Encode(m) })) + r.HandleFunc("/v1/lotus/imap", func(rw http.ResponseWriter, req *http.Request) { + c, err := wsUpgrade.Upgrade(rw, req, nil) + if err != nil { + log.Println("WebSocket upgrade error:", err) + return + } + defer c.Close() + + for { + var m map[string]any + err = c.ReadJSON(&m) + if err != nil { + log.Println("WebSocket json error:", err) + return + } + if v, ok := m["token"]; ok { + _, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](verify, v.(string)) + if err != nil { + c.WriteMessage(websocket.TextMessage, []byte("Invalid token")) + return + } + b2 := b.Claims.Perms.Search("mail:inbox=*") + if len(b2) != 1 { + c.WriteMessage(websocket.TextMessage, []byte("Invalid mail inbox perm")) + return + } + c.WriteMessage(websocket.TextMessage, []byte(`{"auth":"ok"}`)) + continue + } else if vAct, ok := m["action"]; ok { + switch vAct.(string) { + case "list": + log.Println(m) + if slices.EqualFunc[[]any, []any, any, any](m["args"].([]any), []any{"", "*"}, func(a1, a2 any) bool { + return a1 == a2 + }) { + c.WriteMessage(websocket.TextMessage, []byte(` +{ + "type": "list", + "value": [ + {"Attributes": ["\\HasChildren", "\\UnMarked", "\\Archive"], "Delimiter": "/", "Name": "Archive"}, + {"Attributes": ["\\HasNoChildren", "\\UnMarked"], "Delimiter": "/", "Name": "Archive/2022"}, + {"Attributes": ["\\HasNoChildren", "\\UnMarked"], "Delimiter": "/", "Name": "Archive/2023"}, + {"Attributes": ["\\HasNoChildren", "\\UnMarked", "\\Junk"], "Delimiter": "/", "Name": "Junk"}, + {"Attributes": ["\\HasChildren", "\\Trash"], "Delimiter": "/", "Name": "Trash"}, + {"Attributes": ["\\HasNoChildren", "\\UnMarked"], "Delimiter": "/", "Name": "INBOX/status"}, + {"Attributes": ["\\HasNoChildren", "\\UnMarked"], "Delimiter": "/", "Name": "INBOX/hello"}, + {"Attributes": ["\\HasNoChildren", "\\UnMarked"], "Delimiter": "/", "Name": "INBOX/hi"}, + {"Attributes": ["\\Noselect", "\\HasChildren"], "Delimiter": "/", "Name": "INBOX/test/sub/folder"}, + {"Attributes": ["\\HasNoChildren"], "Delimiter": "/", "Name": "INBOX/test/sub/folder/something"}, + {"Attributes": ["\\HasNoChildren", "\\UnMarked", "\\Drafts"], "Delimiter": "/", "Name": "Drafts"}, + {"Attributes": ["\\HasNoChildren", "\\Sent"], "Delimiter": "/", "Name": "Sent"}, + {"Attributes": ["\\HasChildren"], "Delimiter": "/", "Name": "INBOX"} + ] +} +`)) + } + continue + } + } + } + }) logger := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { log.Println("[API Server]", req.URL.String()) r.ServeHTTP(rw, req) }) - http.ListenAndServe(":9095", serveApiCors.Handler(logger)) - log.Println("[API Server]", http.ListenAndServe(":9090", r)) + log.Println("[API Server]", http.ListenAndServe(":9095", serveApiCors.Handler(logger))) } func hasPerm(verify mjwt.Verifier, perm string, next func(rw http.ResponseWriter, req *http.Request)) http.Handler {