fully working refresh tokens. No more expiring :)
This commit is contained in:
@ -36,6 +36,14 @@ type codeReturn struct {
|
|||||||
Refresh string `json:"refresh"`
|
Refresh string `json:"refresh"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type refreshBody struct {
|
||||||
|
Refresh string `json:"refresh"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type refreshReturn struct {
|
||||||
|
Access string `json:"access"`
|
||||||
|
}
|
||||||
|
|
||||||
func (h *AuthHandler) login(body loginBody, w http.ResponseWriter, r *http.Request) {
|
func (h *AuthHandler) login(body loginBody, w http.ResponseWriter, r *http.Request) {
|
||||||
err := h.auth.CreateCode(body.Email)
|
err := h.auth.CreateCode(body.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -78,6 +86,22 @@ func (h *AuthHandler) code(body codeBody, w http.ResponseWriter, r *http.Request
|
|||||||
middleware.WriteJsonOrError(h.logger, codeReturn, w)
|
middleware.WriteJsonOrError(h.logger, codeReturn, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *AuthHandler) refresh(body refreshBody, w http.ResponseWriter, r *http.Request) {
|
||||||
|
userId, err := h.jwtManager.GetUserIdFromRefresh(body.Refresh)
|
||||||
|
if err != nil {
|
||||||
|
middleware.WriteErrorBadRequest(h.logger, "invalid refresh token: "+err.Error(), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
access := h.jwtManager.CreateAccessToken(userId)
|
||||||
|
|
||||||
|
refreshReturn := refreshReturn{
|
||||||
|
Access: access,
|
||||||
|
}
|
||||||
|
|
||||||
|
middleware.WriteJsonOrError(h.logger, refreshReturn, w)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *AuthHandler) CreateRoutes(r chi.Router) {
|
func (h *AuthHandler) CreateRoutes(r chi.Router) {
|
||||||
h.logger.Info("Mounting auth router")
|
h.logger.Info("Mounting auth router")
|
||||||
|
|
||||||
@ -86,6 +110,7 @@ func (h *AuthHandler) CreateRoutes(r chi.Router) {
|
|||||||
|
|
||||||
r.Post("/login", middleware.WithValidatedPost(h.login))
|
r.Post("/login", middleware.WithValidatedPost(h.login))
|
||||||
r.Post("/code", middleware.WithValidatedPost(h.code))
|
r.Post("/code", middleware.WithValidatedPost(h.code))
|
||||||
|
r.Post("/refresh", middleware.WithValidatedPost(h.refresh))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -19,7 +18,7 @@ const (
|
|||||||
type JwtClaims struct {
|
type JwtClaims struct {
|
||||||
UserID string
|
UserID string
|
||||||
Type JwtType
|
Type JwtType
|
||||||
Expire time.Time
|
Expiry time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type JwtManager struct {
|
type JwtManager struct {
|
||||||
@ -34,7 +33,7 @@ func (jm *JwtManager) createToken(claims JwtClaims) *jwt.Token {
|
|||||||
return jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
return jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
"UserID": claims.UserID,
|
"UserID": claims.UserID,
|
||||||
"Type": claims.Type,
|
"Type": claims.Type,
|
||||||
"Expire": claims.Expire,
|
"exp": claims.Expiry.Unix(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ func (jm *JwtManager) CreateRefreshToken(userId uuid.UUID) string {
|
|||||||
token := jm.createToken(JwtClaims{
|
token := jm.createToken(JwtClaims{
|
||||||
UserID: userId.String(),
|
UserID: userId.String(),
|
||||||
Type: Refresh,
|
Type: Refresh,
|
||||||
Expire: time.Now().Add(time.Hour * 24 * 30),
|
Expiry: time.Now().Add(time.Hour * 24 * 30),
|
||||||
})
|
})
|
||||||
|
|
||||||
tokenString, err := token.SignedString(jm.secret)
|
tokenString, err := token.SignedString(jm.secret)
|
||||||
@ -57,7 +56,7 @@ func (jm *JwtManager) CreateAccessToken(userId uuid.UUID) string {
|
|||||||
token := jm.createToken(JwtClaims{
|
token := jm.createToken(JwtClaims{
|
||||||
UserID: userId.String(),
|
UserID: userId.String(),
|
||||||
Type: Access,
|
Type: Access,
|
||||||
Expire: time.Now().Add(time.Minute),
|
Expiry: time.Now().Add(time.Minute),
|
||||||
})
|
})
|
||||||
|
|
||||||
tokenString, err := token.SignedString(jm.secret)
|
tokenString, err := token.SignedString(jm.secret)
|
||||||
@ -79,7 +78,7 @@ func (jm *JwtManager) GetUserIdFromAccess(accessToken string) (uuid.UUID, error)
|
|||||||
return uuid.Nil, err
|
return uuid.Nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if token is valid (including expiry check)
|
// Check if token is valid (JWT library validates exp claim automatically)
|
||||||
if !token.Valid {
|
if !token.Valid {
|
||||||
return uuid.Nil, NotValidToken
|
return uuid.Nil, NotValidToken
|
||||||
}
|
}
|
||||||
@ -90,27 +89,34 @@ func (jm *JwtManager) GetUserIdFromAccess(accessToken string) (uuid.UUID, error)
|
|||||||
return uuid.Nil, NotValidToken
|
return uuid.Nil, NotValidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional explicit expiry check
|
userId, err := uuid.Parse(claims["UserID"].(string))
|
||||||
expireClaim, ok := claims["Expire"]
|
if err != nil {
|
||||||
if !ok {
|
|
||||||
return uuid.Nil, NotValidToken
|
return uuid.Nil, NotValidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
var expireTime time.Time
|
return userId, nil
|
||||||
switch exp := expireClaim.(type) {
|
} else {
|
||||||
case float64:
|
return uuid.Nil, NotValidToken
|
||||||
expireTime = time.Unix(int64(exp), 0)
|
}
|
||||||
case json.Number:
|
}
|
||||||
expInt, err := exp.Int64()
|
|
||||||
if err != nil {
|
|
||||||
return uuid.Nil, NotValidToken
|
|
||||||
}
|
|
||||||
expireTime = time.Unix(expInt, 0)
|
|
||||||
default:
|
|
||||||
return uuid.Nil, NotValidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if time.Now().After(expireTime) {
|
func (jm *JwtManager) GetUserIdFromRefresh(refreshToken string) (uuid.UUID, error) {
|
||||||
|
token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (any, error) {
|
||||||
|
return jm.secret, nil
|
||||||
|
}, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return uuid.Nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if token is valid (JWT library validates exp claim automatically)
|
||||||
|
if !token.Valid {
|
||||||
|
return uuid.Nil, NotValidToken
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||||
|
tokenType, ok := claims["Type"]
|
||||||
|
if !ok || tokenType.(string) != "refresh" {
|
||||||
return uuid.Nil, NotValidToken
|
return uuid.Nil, NotValidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,22 +4,22 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "haystack",
|
"name": "haystack",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "^0.13.10",
|
"@kobalte/core": "^0.13.11",
|
||||||
"@kobalte/tailwindcss": "^0.9.0",
|
"@kobalte/tailwindcss": "^0.9.0",
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"@tabler/icons-solidjs": "^3.34.0",
|
"@tabler/icons-solidjs": "^3.35.0",
|
||||||
"@tanstack/solid-virtual": "^3.13.12",
|
"@tanstack/solid-virtual": "^3.13.12",
|
||||||
"@tauri-apps/api": "^2.6.0",
|
"@tauri-apps/api": "^2.8.0",
|
||||||
"@tauri-apps/plugin-dialog": "~2.3.0",
|
"@tauri-apps/plugin-dialog": "~2.3.3",
|
||||||
"@tauri-apps/plugin-fs": "~2.4.0",
|
"@tauri-apps/plugin-fs": "~2.4.2",
|
||||||
"@tauri-apps/plugin-http": "2.4.3",
|
"@tauri-apps/plugin-http": "^2.4.3",
|
||||||
"@tauri-apps/plugin-log": "~2.6.0",
|
"@tauri-apps/plugin-log": "^2.6.0",
|
||||||
"@tauri-apps/plugin-opener": "^2.4.0",
|
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||||
"@tauri-apps/plugin-os": "2.2.1",
|
"@tauri-apps/plugin-os": "^2.2.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"solid-js": "^1.9.7",
|
"solid-js": "^1.9.9",
|
||||||
"solid-markdown": "^2.0.14",
|
"solid-markdown": "^2.0.14",
|
||||||
"solid-motionone": "^1.0.4",
|
"solid-motionone": "^1.0.4",
|
||||||
"solidjs-markdown": "^0.2.0",
|
"solidjs-markdown": "^0.2.0",
|
||||||
@ -30,15 +30,15 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.4",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@tauri-apps/cli": "^2.6.2",
|
"@tauri-apps/cli": "^2.8.4",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"postcss-cli": "^11.0.1",
|
"postcss-cli": "^11.0.1",
|
||||||
"tailwindcss": "3.4.0",
|
"tailwindcss": "3.4.0",
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.6.3",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.6",
|
||||||
"vite-plugin-solid": "^2.11.7",
|
"vite-plugin-solid": "^2.11.8",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -174,7 +174,7 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||||
|
|
||||||
"@kobalte/core": ["@kobalte/core@0.13.10", "", { "dependencies": { "@floating-ui/dom": "^1.5.1", "@internationalized/date": "^3.4.0", "@internationalized/number": "^3.2.1", "@kobalte/utils": "^0.9.1", "@solid-primitives/props": "^3.1.8", "@solid-primitives/resize-observer": "^2.0.26", "solid-presence": "^0.1.8", "solid-prevent-scroll": "^0.1.4" }, "peerDependencies": { "solid-js": "^1.8.15" } }, "sha512-lzP64ThxZqZB6O6MnMq6w7DxK38o2ClbW3Ob6afUI6p86cUMz5Hb4rdysvYI6m1TKYlOAlFODKkoRznqybQohw=="],
|
"@kobalte/core": ["@kobalte/core@0.13.11", "", { "dependencies": { "@floating-ui/dom": "^1.5.1", "@internationalized/date": "^3.4.0", "@internationalized/number": "^3.2.1", "@kobalte/utils": "^0.9.1", "@solid-primitives/props": "^3.1.8", "@solid-primitives/resize-observer": "^2.0.26", "solid-presence": "^0.1.8", "solid-prevent-scroll": "^0.1.4" }, "peerDependencies": { "solid-js": "^1.8.15" } }, "sha512-hK7TYpdib/XDb/r/4XDBFaO9O+3ZHz4ZWryV4/3BfES+tSQVgg2IJupDnztKXB0BqbSRy/aWlHKw1SPtNPYCFQ=="],
|
||||||
|
|
||||||
"@kobalte/tailwindcss": ["@kobalte/tailwindcss@0.9.0", "", { "peerDependencies": { "tailwindcss": "^3.3.3" } }, "sha512-WbueJTVRiO4yrmfHIBwp07y3M5iibJ/gauEAQ7mOyg1tZulvpO7SM/UdgzX95a9a0KDt1mQFxwO7RmpOUXWOWA=="],
|
"@kobalte/tailwindcss": ["@kobalte/tailwindcss@0.9.0", "", { "peerDependencies": { "tailwindcss": "^3.3.3" } }, "sha512-WbueJTVRiO4yrmfHIBwp07y3M5iibJ/gauEAQ7mOyg1tZulvpO7SM/UdgzX95a9a0KDt1mQFxwO7RmpOUXWOWA=="],
|
||||||
|
|
||||||
@ -272,51 +272,51 @@
|
|||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
|
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
|
||||||
|
|
||||||
"@tabler/icons": ["@tabler/icons@3.34.0", "", {}, "sha512-jtVqv0JC1WU2TTEBN32D9+R6mc1iEBuPwLnBsWaR02SIEciu9aq5806AWkCHuObhQ4ERhhXErLEK7Fs+tEZxiA=="],
|
"@tabler/icons": ["@tabler/icons@3.35.0", "", {}, "sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ=="],
|
||||||
|
|
||||||
"@tabler/icons-solidjs": ["@tabler/icons-solidjs@3.34.0", "", { "dependencies": { "@tabler/icons": "3.34.0" }, "peerDependencies": { "solid-js": "^1.4.7" } }, "sha512-O6RI1dz4o2MhsyMUk4tELySY25deyB+cHsREwQdYynB+8K9CncVgi9vlpG7lE14lmJ64edduDpCkMxqKdev5jQ=="],
|
"@tabler/icons-solidjs": ["@tabler/icons-solidjs@3.35.0", "", { "dependencies": { "@tabler/icons": "3.35.0" }, "peerDependencies": { "solid-js": "^1.4.7" } }, "sha512-9kJxO7ITryM30xgmXJgYkebGXRjXIKIwue5g8AQfk+z0eNLFZqWz5w1833KPSNy/2k/86Pe0IOZJ4Gav3Th5xw=="],
|
||||||
|
|
||||||
"@tanstack/solid-virtual": ["@tanstack/solid-virtual@3.13.12", "", { "dependencies": { "@tanstack/virtual-core": "3.13.12" }, "peerDependencies": { "solid-js": "^1.3.0" } }, "sha512-0dS8GkBTmbuM9cUR6Jni0a45eJbd32CAEbZj8HrZMWIj3lu974NpGz5ywcomOGJ9GdeHuDaRzlwtonBbKV1ihQ=="],
|
"@tanstack/solid-virtual": ["@tanstack/solid-virtual@3.13.12", "", { "dependencies": { "@tanstack/virtual-core": "3.13.12" }, "peerDependencies": { "solid-js": "^1.3.0" } }, "sha512-0dS8GkBTmbuM9cUR6Jni0a45eJbd32CAEbZj8HrZMWIj3lu974NpGz5ywcomOGJ9GdeHuDaRzlwtonBbKV1ihQ=="],
|
||||||
|
|
||||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.12", "", {}, "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA=="],
|
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.12", "", {}, "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA=="],
|
||||||
|
|
||||||
"@tauri-apps/api": ["@tauri-apps/api@2.6.0", "", {}, "sha512-hRNcdercfgpzgFrMXWwNDBN0B7vNzOzRepy6ZAmhxi5mDLVPNrTpo9MGg2tN/F7JRugj4d2aF7E1rtPXAHaetg=="],
|
"@tauri-apps/api": ["@tauri-apps/api@2.8.0", "", {}, "sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli": ["@tauri-apps/cli@2.6.2", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.6.2", "@tauri-apps/cli-darwin-x64": "2.6.2", "@tauri-apps/cli-linux-arm-gnueabihf": "2.6.2", "@tauri-apps/cli-linux-arm64-gnu": "2.6.2", "@tauri-apps/cli-linux-arm64-musl": "2.6.2", "@tauri-apps/cli-linux-riscv64-gnu": "2.6.2", "@tauri-apps/cli-linux-x64-gnu": "2.6.2", "@tauri-apps/cli-linux-x64-musl": "2.6.2", "@tauri-apps/cli-win32-arm64-msvc": "2.6.2", "@tauri-apps/cli-win32-ia32-msvc": "2.6.2", "@tauri-apps/cli-win32-x64-msvc": "2.6.2" }, "bin": { "tauri": "tauri.js" } }, "sha512-s1/eyBHxk0wG1blLeOY2IDjgZcxVrkxU5HFL8rNDwjYGr0o7yr3RAtwmuUPhz13NO+xGAL1bJZaLFBdp+5joKg=="],
|
"@tauri-apps/cli": ["@tauri-apps/cli@2.8.4", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.8.4", "@tauri-apps/cli-darwin-x64": "2.8.4", "@tauri-apps/cli-linux-arm-gnueabihf": "2.8.4", "@tauri-apps/cli-linux-arm64-gnu": "2.8.4", "@tauri-apps/cli-linux-arm64-musl": "2.8.4", "@tauri-apps/cli-linux-riscv64-gnu": "2.8.4", "@tauri-apps/cli-linux-x64-gnu": "2.8.4", "@tauri-apps/cli-linux-x64-musl": "2.8.4", "@tauri-apps/cli-win32-arm64-msvc": "2.8.4", "@tauri-apps/cli-win32-ia32-msvc": "2.8.4", "@tauri-apps/cli-win32-x64-msvc": "2.8.4" }, "bin": { "tauri": "tauri.js" } }, "sha512-ejUZBzuQRcjFV+v/gdj/DcbyX/6T4unZQjMSBZwLzP/CymEjKcc2+Fc8xTORThebHDUvqoXMdsCZt8r+hyN15g=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.6.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YlvT+Yb7u2HplyN2Cf/nBplCQARC/I4uedlYHlgtxg6rV7xbo9BvG1jLOo29IFhqA2rOp5w1LtgvVGwsOf2kxw=="],
|
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.8.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BKu8HRkYV01SMTa7r4fLx+wjgtRK8Vep7lmBdHDioP6b8XH3q2KgsAyPWfEZaZIkZ2LY4SqqGARaE9oilNe0oA=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.6.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-21gdPWfv1bP8rkTdCL44in70QcYcPaDM70L+y78N8TkBuC+/+wqnHcwwjzb+mUyck6UoEw2DORagSI/oKKUGJw=="],
|
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.8.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-imb9PfSd/7G6VAO7v1bQ2A3ZH4NOCbhGJFLchxzepGcXf9NKkfun157JH9mko29K6sqAwuJ88qtzbKCbWJTH9g=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.6.2", "", { "os": "linux", "cpu": "arm" }, "sha512-MW8Y6HqHS5yzQkwGoLk/ZyE1tWpnz/seDoY4INsbvUZdknuUf80yn3H+s6eGKtT/0Bfqon/W9sY7pEkgHRPQgA=="],
|
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.8.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Ml215UnDdl7/fpOrF1CNovym/KjtUbCuPgrcZ4IhqUCnhZdXuphud/JT3E8X97Y03TZ40Sjz8raXYI2ET0exzw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-9PdINTUtnyrnQt9hvC4y1m0NoxKSw/wUB9OTBAQabPj8WLAdvySWiUpEiqJjwLhlu4T6ltXZRpNTEzous3/RXg=="],
|
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.8.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-pbcgBpMyI90C83CxE5REZ9ODyIlmmAPkkJXtV398X3SgZEIYy5TACYqlyyv2z5yKgD8F8WH4/2fek7+jH+ZXAw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrcJTRr7FrtQlTDkYaRXIGo/8YU/xkWmBPC646WwKNZ/S6yqCiDcOMoPe7Cx4ZvcG6sK6LUCLQMfaSNEL7PT0A=="],
|
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.8.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-zumFeaU1Ws5Ay872FTyIm7z8kfzEHu8NcIn8M6TxbJs0a7GRV21KBdpW1zNj2qy7HynnpQCqjAYXTUUmm9JAOw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.6.2", "", { "os": "linux", "cpu": "none" }, "sha512-GnTshO/BaZ9KGIazz2EiFfXGWgLur5/pjqklRA/ck42PGdUQJhV/Ao7A7TdXPjqAzpFxNo6M/Hx0GCH2iMS7IA=="],
|
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.8.4", "", { "os": "linux", "cpu": "none" }, "sha512-qiqbB3Zz6IyO201f+1ojxLj65WYj8mixL5cOMo63nlg8CIzsP23cPYUrx1YaDPsCLszKZo7tVs14pc7BWf+/aQ=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-QDG3WeJD6UJekmrtVPCJRzlKgn9sGzhvD58oAw5gIU+DRovgmmG2U1jH9fS361oYGjWWO7d/KM9t0kugZzi4lQ=="],
|
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.8.4", "", { "os": "linux", "cpu": "x64" }, "sha512-TaqaDd9Oy6k45Hotx3pOf+pkbsxLaApv4rGd9mLuRM1k6YS/aw81YrsMryYPThrxrScEIUcmNIHaHsLiU4GMkw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-TNVTDDtnWzuVqWBFdZ4+8ZTg17tc21v+CT5XBQ+KYCoYtCrIaHpW04fS5Tmudi+vYdBwoPDfwpKEB6LhCeFraQ=="],
|
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.8.4", "", { "os": "linux", "cpu": "x64" }, "sha512-ot9STAwyezN8w+bBHZ+bqSQIJ0qPZFlz/AyscpGqB/JnJQVDFQcRDmUPFEaAtt2UUHSWzN3GoTJ5ypqLBp2WQA=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.6.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-z77C1oa/hMLO/jM1JF39tK3M3v9nou7RsBnQoOY54z5WPcpVAbS0XdFhXB7sSN72BOiO3moDky9lQANQz6L3CA=="],
|
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.8.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-+2aJ/g90dhLiOLFSD1PbElXX3SoMdpO7HFPAZB+xot3CWlAZD1tReUFy7xe0L5GAR16ZmrxpIDM9v9gn5xRy/w=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.6.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-TmD8BbzbjluBw8+QEIWUVmFa9aAluSkT1N937n1mpYLXcPbTpbunqRFiIznTwupoJNJIdtpF/t7BdZDRh5rrcg=="],
|
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.8.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-yj7WDxkL1t9Uzr2gufQ1Hl7hrHuFKTNEOyascbc109EoiAqCp0tgZ2IykQqOZmZOHU884UAWI1pVMqBhS/BfhA=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.6.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ItB8RCKk+nCmqOxOvbNtltz6x1A4QX6cSM21kj3NkpcnjT9rHSMcfyf8WVI2fkoMUJR80iqCblUX6ARxC3lj6w=="],
|
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.8.4", "", { "os": "win32", "cpu": "x64" }, "sha512-XuvGB4ehBdd7QhMZ9qbj/8icGEatDuBNxyYHbLKsTYh90ggUlPa/AtaqcC1Fo69lGkTmq9BOKrs1aWSi7xDonA=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.3.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-ylSBvYYShpGlKKh732ZuaHyJ5Ie1JR71QCXewCtsRLqGdc8Is4xWdz6t43rzXyvkItM9syNPMvFVcvjgEy+/GA=="],
|
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-cWXB9QJDbLIA0v7I5QY183awazBEQNPhp19iPvrMZoJRX8SbFkhWFx1/q7zy7xGpXXzxz29qtq6z21Ho7W5Iew=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-Sp8AdDcbyXyk6LD6Pmdx44SH3LPeNAvxR2TFfq/8CwqzfO1yOyV+RzT8fov0NNN7d9nvW7O7MtMAptJ42YXA5g=="],
|
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-YGhmYuTgXGsi6AjoV+5mh2NvicgWBfVJHHheuck6oHD+HC9bVWPaHvCP0/Aw4pHDejwrvT8hE3+zZAaWf+hrig=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.4.3", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-Us8X+FikzpaZRNr4kH4HLwyXascHbM42p6LxAqRTQnHPrrqp1usaH4vxWAZalPvTbHJ3gBEMJPHusFJgtjGJjA=="],
|
"@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-x1mQKHSLDk4mS2S938OTeyk8L7QyLpCrKZCZcjkljGsvTvRMojCvI9SeJ1kaxc7t8xSilkC7WdId8xER9TIGLg=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-log": ["@tauri-apps/plugin-log@2.6.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-gVp3l31akA1Jk2bZsTA0hMFD5/gLe49Nw1btu5lViau0QqgC2XyT79LSwvy7a44ewtQbSexchqIg7oTJKMIbXQ=="],
|
"@tauri-apps/plugin-log": ["@tauri-apps/plugin-log@2.7.0", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-81XQ2f93x4vmIB5OY0XlYAxy60cHdYLs0Ki8Qp38tNATRiuBit+Orh3frpY3qfYQnqEvYVyRub7YRJWlmW2RRA=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-43VyN8JJtvKWJY72WI/KNZszTpDpzHULFxQs0CJBIYUdCRowQ6Q1feWTDb979N7nldqSuDOaBupZ6wz2nvuWwQ=="],
|
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.0", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-B0LShOYae4CZjN8leiNDbnfjSrTwoZakqKaWpfoH6nXiJwt6Rgj6RnVIffG3DoJiKsffRhMkjmBV9VeilSb4TA=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-cNYpNri2CCc6BaNeB6G/mOtLvg8dFyFQyCUdf2y0K8PIAKGEWdEcu8DECkydU2B+oj4OJihDPD2de5K6cbVl9A=="],
|
"@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ty5V8XDUIFbSnrk3zsFoP3kzN+vAufYzalJSlmrVhQTImIZa1aL1a03bOaP2vuBvfR+WDRC6NgV2xBl8G07d+w=="],
|
||||||
|
|
||||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||||
|
|
||||||
@ -672,7 +672,7 @@
|
|||||||
|
|
||||||
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
|
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
|
||||||
|
|
||||||
"solid-js": ["solid-js@1.9.7", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw=="],
|
"solid-js": ["solid-js@1.9.9", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA=="],
|
||||||
|
|
||||||
"solid-jsx": ["solid-jsx@0.9.1", "", { "peerDependencies": { "solid-js": "^1.4.0" } }, "sha512-HHTx58rx3tqg5LMGuQnaE1vqZjpl+RMP0jYQnBkTY0xKIASVNSLZJCZoPFrpKH8wWWYyTLHdepgzs8u/e6yz5Q=="],
|
"solid-jsx": ["solid-jsx@0.9.1", "", { "peerDependencies": { "solid-js": "^1.4.0" } }, "sha512-HHTx58rx3tqg5LMGuQnaE1vqZjpl+RMP0jYQnBkTY0xKIASVNSLZJCZoPFrpKH8wWWYyTLHdepgzs8u/e6yz5Q=="],
|
||||||
|
|
||||||
@ -766,9 +766,9 @@
|
|||||||
|
|
||||||
"vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="],
|
"vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="],
|
||||||
|
|
||||||
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
"vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="],
|
||||||
|
|
||||||
"vite-plugin-solid": ["vite-plugin-solid@2.11.7", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-5TgK1RnE449g0Ryxb9BXqem89RSy7fE8XGVCo+Gw84IHgPuPVP7nYNP6WBVAaY/0xw+OqfdQee+kusL0y3XYNg=="],
|
"vite-plugin-solid": ["vite-plugin-solid@2.11.8", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-hFrCxBfv3B1BmFqnJF4JOCYpjrmi/zwyeKjcomQ0khh8HFyQ8SbuBWQ7zGojfrz6HUOBFrJBNySDi/JgAHytWg=="],
|
||||||
|
|
||||||
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
|
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
|
||||||
|
|
||||||
@ -796,10 +796,6 @@
|
|||||||
|
|
||||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-http/@tauri-apps/api": ["@tauri-apps/api@2.5.0", "", {}, "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA=="],
|
|
||||||
|
|
||||||
"@tauri-apps/plugin-os/@tauri-apps/api": ["@tauri-apps/api@2.5.0", "", {}, "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA=="],
|
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
||||||
|
@ -14,22 +14,22 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "^0.13.10",
|
"@kobalte/core": "^0.13.11",
|
||||||
"@kobalte/tailwindcss": "^0.9.0",
|
"@kobalte/tailwindcss": "^0.9.0",
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"@tabler/icons-solidjs": "^3.34.0",
|
"@tabler/icons-solidjs": "^3.35.0",
|
||||||
"@tanstack/solid-virtual": "^3.13.12",
|
"@tanstack/solid-virtual": "^3.13.12",
|
||||||
"@tauri-apps/api": "^2.6.0",
|
"@tauri-apps/api": "^2.8.0",
|
||||||
"@tauri-apps/plugin-dialog": "~2.3.0",
|
"@tauri-apps/plugin-dialog": "~2.3.3",
|
||||||
"@tauri-apps/plugin-fs": "~2.4.0",
|
"@tauri-apps/plugin-fs": "~2.4.2",
|
||||||
"@tauri-apps/plugin-http": "2.4.3",
|
"@tauri-apps/plugin-http": "^2.5.2",
|
||||||
"@tauri-apps/plugin-log": "~2.6.0",
|
"@tauri-apps/plugin-log": "^2.7.0",
|
||||||
"@tauri-apps/plugin-opener": "^2.4.0",
|
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||||
"@tauri-apps/plugin-os": "2.2.1",
|
"@tauri-apps/plugin-os": "^2.3.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"solid-js": "^1.9.7",
|
"solid-js": "^1.9.9",
|
||||||
"solid-markdown": "^2.0.14",
|
"solid-markdown": "^2.0.14",
|
||||||
"solid-motionone": "^1.0.4",
|
"solid-motionone": "^1.0.4",
|
||||||
"solidjs-markdown": "^0.2.0",
|
"solidjs-markdown": "^0.2.0",
|
||||||
@ -40,15 +40,15 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.4",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@tauri-apps/cli": "^2.6.2",
|
"@tauri-apps/cli": "^2.8.4",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"postcss-cli": "^11.0.1",
|
"postcss-cli": "^11.0.1",
|
||||||
"tailwindcss": "3.4.0",
|
"tailwindcss": "3.4.0",
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.6.3",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.6",
|
||||||
"vite-plugin-solid": "^2.11.7",
|
"vite-plugin-solid": "^2.11.8",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1675
frontend/src-tauri/Cargo.lock
generated
1675
frontend/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -3,44 +3,57 @@ import { platform } from "@tauri-apps/plugin-os";
|
|||||||
import { jwtDecode } from "jwt-decode";
|
import { jwtDecode } from "jwt-decode";
|
||||||
import { Component, ParentProps, Show } from "solid-js";
|
import { Component, ParentProps, Show } from "solid-js";
|
||||||
import { save_token } from "tauri-plugin-ios-shared-token-api";
|
import { save_token } from "tauri-plugin-ios-shared-token-api";
|
||||||
|
import { InferOutput, literal, number, object, parse, pipe, string, transform } from "valibot";
|
||||||
|
|
||||||
export const isTokenValid = (): boolean => {
|
export const isTokenValid = (): boolean => {
|
||||||
const token = localStorage.getItem("access");
|
const token = localStorage.getItem("access");
|
||||||
|
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
jwtDecode(token);
|
jwtDecode(token);
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const accessTokenPropertiesValidator = object({
|
||||||
|
UserID: string(),
|
||||||
|
Type: literal('access'),
|
||||||
|
exp: pipe(number(), transform(i => new Date(i)))
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getTokenProperties = (token: string): InferOutput<typeof accessTokenPropertiesValidator> => {
|
||||||
|
const decoded = jwtDecode(token);
|
||||||
|
|
||||||
|
return parse(accessTokenPropertiesValidator, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
export const ProtectedRoute: Component<ParentProps> = (props) => {
|
export const ProtectedRoute: Component<ParentProps> = (props) => {
|
||||||
const isValid = isTokenValid();
|
const isValid = isTokenValid();
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
const token = localStorage.getItem("access");
|
const token = localStorage.getItem("access");
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
throw new Error("unreachable");
|
throw new Error("unreachable");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform() === "ios") {
|
if (platform() === "ios") {
|
||||||
// iOS share extension is a seperate process to the App.
|
// iOS share extension is a seperate process to the App.
|
||||||
// Therefore, we need to share our access token somewhere both the App & Share Extension can access
|
// Therefore, we need to share our access token somewhere both the App & Share Extension can access
|
||||||
// This involves App Groups.
|
// This involves App Groups.
|
||||||
save_token(token)
|
save_token(token)
|
||||||
.then(() => console.log("Saved token!!!"))
|
.then(() => console.log("Saved token!!!"))
|
||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={isValid} fallback={<Navigate href="/login" />}>
|
<Show when={isValid} fallback={<Navigate href="/login" />}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Show>
|
</Show>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import { getTokenProperties } from "@components/protected-route";
|
||||||
import { fetch } from "@tauri-apps/plugin-http";
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type InferOutput,
|
type InferOutput,
|
||||||
@ -6,6 +8,7 @@ import {
|
|||||||
literal,
|
literal,
|
||||||
null_,
|
null_,
|
||||||
nullable,
|
nullable,
|
||||||
|
parse,
|
||||||
pipe,
|
pipe,
|
||||||
safeParse,
|
safeParse,
|
||||||
strictObject,
|
strictObject,
|
||||||
@ -31,14 +34,40 @@ const getBaseRequest = ({ path, body, method }: BaseRequestParams): Request => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBaseAuthorizedRequest = ({
|
const refreshTokenValidator = strictObject({
|
||||||
|
access: string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const getBaseAuthorizedRequest = async ({
|
||||||
path,
|
path,
|
||||||
body,
|
body,
|
||||||
method,
|
method,
|
||||||
}: BaseRequestParams): Request => {
|
}: BaseRequestParams): Promise<Request> => {
|
||||||
|
let accessToken = localStorage.getItem("access")?.toString();
|
||||||
|
const refreshToken = localStorage.getItem("refresh")?.toString();
|
||||||
|
|
||||||
|
if (accessToken == null && refreshToken == null) {
|
||||||
|
throw new Error("your are not logged in")
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidAccessToken = accessToken != null && getTokenProperties(accessToken).exp.getTime() > Date.now()
|
||||||
|
|
||||||
|
if (!isValidAccessToken) {
|
||||||
|
const newAccessToken = await fetch(getBaseRequest({
|
||||||
|
path: 'auth/refresh', method: "POST", body: JSON.stringify({
|
||||||
|
refresh: refreshToken,
|
||||||
|
})
|
||||||
|
})).then(r => r.json());
|
||||||
|
|
||||||
|
const { access } = parse(refreshTokenValidator, newAccessToken);
|
||||||
|
|
||||||
|
localStorage.setItem("access", access);
|
||||||
|
accessToken = access
|
||||||
|
}
|
||||||
|
|
||||||
return new Request(`${base}/${path}`, {
|
return new Request(`${base}/${path}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${localStorage.getItem("access")?.toString()}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
},
|
},
|
||||||
body,
|
body,
|
||||||
method,
|
method,
|
||||||
@ -55,7 +84,7 @@ export const sendImageFile = async (
|
|||||||
imageName: string,
|
imageName: string,
|
||||||
file: File,
|
file: File,
|
||||||
): Promise<InferOutput<typeof sendImageResponseValidator>> => {
|
): Promise<InferOutput<typeof sendImageResponseValidator>> => {
|
||||||
const request = getBaseAuthorizedRequest({
|
const request = await getBaseAuthorizedRequest({
|
||||||
path: `images/${imageName}`,
|
path: `images/${imageName}`,
|
||||||
body: file,
|
body: file,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -77,7 +106,7 @@ export const sendImageFile = async (
|
|||||||
export const deleteImage = async (
|
export const deleteImage = async (
|
||||||
imageID: string
|
imageID: string
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const request = getBaseAuthorizedRequest({
|
const request = await getBaseAuthorizedRequest({
|
||||||
path: `images/${imageID}`,
|
path: `images/${imageID}`,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
@ -86,7 +115,7 @@ export const deleteImage = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const deleteImageFromStack = async (listID: string, imageID: string): Promise<void> => {
|
export const deleteImageFromStack = async (listID: string, imageID: string): Promise<void> => {
|
||||||
const request = getBaseAuthorizedRequest({
|
const request = await getBaseAuthorizedRequest({
|
||||||
path: `stacks/${listID}/${imageID}`,
|
path: `stacks/${listID}/${imageID}`,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
@ -104,7 +133,7 @@ export const sendImage = async (
|
|||||||
imageName: string,
|
imageName: string,
|
||||||
base64Image: string,
|
base64Image: string,
|
||||||
): Promise<InferOutput<typeof sendImageResponseValidator>> => {
|
): Promise<InferOutput<typeof sendImageResponseValidator>> => {
|
||||||
const request = getBaseAuthorizedRequest({
|
const request = await getBaseAuthorizedRequest({
|
||||||
path: `images/${imageName}`,
|
path: `images/${imageName}`,
|
||||||
body: base64Image,
|
body: base64Image,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -222,7 +251,7 @@ export type JustTheImageWhatAreTheseNames = InferOutput<
|
|||||||
export const getUserImages = async (): Promise<
|
export const getUserImages = async (): Promise<
|
||||||
InferOutput<typeof imageRequestValidator>
|
InferOutput<typeof imageRequestValidator>
|
||||||
> => {
|
> => {
|
||||||
const request = getBaseAuthorizedRequest({ path: "images" });
|
const request = await getBaseAuthorizedRequest({ path: "images" });
|
||||||
|
|
||||||
const res = await fetch(request).then((res) => res.json());
|
const res = await fetch(request).then((res) => res.json());
|
||||||
|
|
||||||
@ -283,7 +312,7 @@ export const createList = async (
|
|||||||
title: string,
|
title: string,
|
||||||
description: string,
|
description: string,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const request = getBaseAuthorizedRequest({
|
const request = await getBaseAuthorizedRequest({
|
||||||
path: "stacks",
|
path: "stacks",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ title, description }),
|
body: JSON.stringify({ title, description }),
|
||||||
|
@ -52,6 +52,12 @@ export const List: Component = () => {
|
|||||||
|
|
||||||
const { lists, onDeleteImageFromStack } = useSearchImageContext();
|
const { lists, onDeleteImageFromStack } = useSearchImageContext();
|
||||||
|
|
||||||
|
// TODO: make sure this is up to date. Put it behind a resource.
|
||||||
|
const accessToken = localStorage.getItem("access");
|
||||||
|
if (accessToken == null) {
|
||||||
|
return <>Ermm... Access token is not set :(</>
|
||||||
|
}
|
||||||
|
|
||||||
const list = () => lists().find((l) => l.ID === listId);
|
const list = () => lists().find((l) => l.ID === listId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Reference in New Issue
Block a user