From 73e42240a61f0316dbd5ea8ba164d5deee6b878b Mon Sep 17 00:00:00 2001 From: GUO YANKE Date: Thu, 25 Jan 2024 21:17:40 +0800 Subject: [PATCH] feat: complete ssh keys management --- app.go | 65 +++++++++++++++++ go.mod | 2 +- go.sum | 2 - ui/components/skeleton/dashboard.vue | 7 +- ui/composables/key.ts | 7 ++ .../{profile.vue => profile/index.vue} | 0 ui/pages/dashboard/profile/keys/index.vue | 64 +++++++++++++++++ ui/pages/dashboard/profile/keys/new.vue | 70 +++++++++++++++++++ ui/utils/types.ts | 7 ++ 9 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 ui/composables/key.ts rename ui/pages/dashboard/{profile.vue => profile/index.vue} (100%) create mode 100644 ui/pages/dashboard/profile/keys/index.vue create mode 100644 ui/pages/dashboard/profile/keys/new.vue diff --git a/app.go b/app.go index 8b4b248..c1bf399 100644 --- a/app.go +++ b/app.go @@ -13,6 +13,7 @@ import ( "github.com/yankeguo/rg" "github.com/yankeguo/ufx" "go.uber.org/fx" + "golang.org/x/crypto/ssh" "gorm.io/gorm" ) @@ -163,8 +164,72 @@ func (a *App) routeSignOut(c ufx.Context) { c.JSON(map[string]any{}) } +func (a *App) routeListKeys(c ufx.Context) { + _, user := a.requireUser(c) + + db := dao.Use(a.db) + + keys := rg.Must(db.Key.Where(db.Key.UserID.Eq(user.ID)).Find()) + + c.JSON(map[string]any{"keys": keys}) +} + +func (a *App) routeCreateKey(c ufx.Context) { + _, user := a.requireUser(c) + + var data struct { + DisplayName string `json:"display_name"` + PublicKey string `json:"public_key"` + } + c.Bind(&data) + + if data.DisplayName == "" { + data.DisplayName = "Unnamed" + } + + if data.PublicKey == "" { + halt.String("public key is required", halt.WithBadRequest()) + return + } + + k, _, _, _ := rg.Must4(ssh.ParseAuthorizedKey([]byte(data.PublicKey))) + + id := ssh.FingerprintSHA256(k) + + db := dao.Use(a.db) + + key := &model.Key{ + ID: id, + DisplayName: data.DisplayName, + UserID: user.ID, + CreatedAt: time.Now(), + } + + rg.Must0(db.Key.Create(key)) + + c.JSON(map[string]any{"key": key}) +} + +func (a *App) routeDeleteKey(c ufx.Context) { + _, user := a.requireUser(c) + + var data struct { + ID string `json:"id"` + } + c.Bind(&data) + + db := dao.Use(a.db) + + rg.Must(db.Key.Where(db.Key.ID.Eq(data.ID), db.Key.UserID.Eq(user.ID)).Delete()) + + c.JSON(map[string]any{}) +} + func InstallAppToRouter(a *App, ur ufx.Router) { ur.HandleFunc("/backend/current_user", a.routeCurrentUser) ur.HandleFunc("/backend/sign_in", a.routeSignIn) ur.HandleFunc("/backend/sign_out", a.routeSignOut) + ur.HandleFunc("/backend/keys", a.routeListKeys) + ur.HandleFunc("/backend/keys/create", a.routeCreateKey) + ur.HandleFunc("/backend/keys/delete", a.routeDeleteKey) } diff --git a/go.mod b/go.mod index 6a93122..72a653c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21.6 require ( github.com/git-lfs/wildmatch v1.0.4 github.com/glebarez/sqlite v1.10.0 + github.com/yankeguo/halt v0.1.0 github.com/yankeguo/rg v1.1.0 github.com/yankeguo/ufx v0.2.2 go.uber.org/fx v1.20.1 @@ -41,7 +42,6 @@ require ( github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/yankeguo/halt v0.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect go.opentelemetry.io/contrib/propagators/b3 v1.22.0 // indirect go.opentelemetry.io/otel v1.22.0 // indirect diff --git a/go.sum b/go.sum index fb97406..42fb014 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= diff --git a/ui/components/skeleton/dashboard.vue b/ui/components/skeleton/dashboard.vue index 1c84a19..40413c7 100644 --- a/ui/components/skeleton/dashboard.vue +++ b/ui/components/skeleton/dashboard.vue @@ -26,7 +26,12 @@ const links = [ ], [ { - label: "Profile", + label: "My SSH Keys", + icon: "i-mdi-key-chain", + to: { name: "dashboard-profile-keys" }, + }, + { + label: "My Profile", icon: "i-mdi-account-circle", to: { name: "dashboard-profile" }, }, diff --git a/ui/composables/key.ts b/ui/composables/key.ts new file mode 100644 index 0000000..8baf703 --- /dev/null +++ b/ui/composables/key.ts @@ -0,0 +1,7 @@ +export const useKeys = () => { + return useAsyncData<{ keys: BKey[] }>("keys", () => $fetch("/backend/keys"), { + default() { + return { keys: [] }; + }, + }); +}; diff --git a/ui/pages/dashboard/profile.vue b/ui/pages/dashboard/profile/index.vue similarity index 100% rename from ui/pages/dashboard/profile.vue rename to ui/pages/dashboard/profile/index.vue diff --git a/ui/pages/dashboard/profile/keys/index.vue b/ui/pages/dashboard/profile/keys/index.vue new file mode 100644 index 0000000..eacf428 --- /dev/null +++ b/ui/pages/dashboard/profile/keys/index.vue @@ -0,0 +1,64 @@ + + + diff --git a/ui/pages/dashboard/profile/keys/new.vue b/ui/pages/dashboard/profile/keys/new.vue new file mode 100644 index 0000000..fea7ba1 --- /dev/null +++ b/ui/pages/dashboard/profile/keys/new.vue @@ -0,0 +1,70 @@ + + + diff --git a/ui/utils/types.ts b/ui/utils/types.ts index 10edf99..b264d39 100644 --- a/ui/utils/types.ts +++ b/ui/utils/types.ts @@ -1,3 +1,10 @@ +export interface BKey { + id: string; + display_name: string; + created_at: string; + visited_at: string; +} + export interface BUser { id: string; created_at: string;