From f4c5bc582387ef523feb1b655324918aa93dab43 Mon Sep 17 00:00:00 2001 From: ChenYunDa Date: Sat, 3 Oct 2026 21:10:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20.gitignore:alterminal=20?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E8=AA=A4=E5=BF=BD=E7=95=A5=20cmd/alterminal,?= =?UTF-8?q?=E8=A3=9C=E6=8F=90=E4=BA=A4=20main=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- cmd/alterminal/main.go | 104 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 cmd/alterminal/main.go diff --git a/.gitignore b/.gitignore index 6cf9e29..ce9a4a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -# Binaries -alterminal +# Binaries(/ 錨定僅忽略根目錄執行檔;未錨定的 alterminal 會連 +# cmd/alterminal 原始碼目錄一併忽略) +/alterminal *.exe # Build tools (Tailwind standalone CLI,下載方式見 README) diff --git a/cmd/alterminal/main.go b/cmd/alterminal/main.go new file mode 100644 index 0000000..674f5c7 --- /dev/null +++ b/cmd/alterminal/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "log" + "net/http" + "os" + "strings" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + + "alterminal/internal/admin" + "alterminal/internal/auth" + "alterminal/internal/cli" + "alterminal/internal/oidc" + "alterminal/internal/store" +) + +func main() { + if len(os.Args) > 1 { + cli.Run(os.Args[1:]) + return + } + + db, err := store.Open() + if err != nil { + log.Fatal("database: ", err) + } + + // issuer 須為含 scheme 的絕對 URL 且不含尾斜線(OIDC Discovery 1.0 + // §3:issuer 字串與簽入 token 的 iss claim 必須完全一致),統一去除 + // 尾斜線以容錯。 + issuer := strings.TrimSuffix(store.EnvOr("ISSUER", "http://localhost:8080"), "/") + + r := chi.NewRouter() + + r.Use(middleware.Logger) + r.Use(middleware.Recoverer) + + r.Get("/", auth.AccountPageHandler(db)) + + r.Get("/users/{name}", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello, " + chi.URLParam(r, "name") + "!")) + }) + + r.Get("/.well-known/openid-configuration", oidc.DiscoveryHandler(issuer)) + r.Get("/.well-known/jwks.json", oidc.JWKSHandler(db)) + + r.Get("/login", auth.LoginPageHandler(db)) + r.Post("/login", auth.LoginHandler(db)) + // RP-Initiated Logout 1.0 §2:登出端點須支援 GET 與 POST。不帶 RP + // 參數的 POST(側欄登出表單、JSON API)由 handler 內部委由 + // auth.LogoutHandler 處理,行為不變。 + r.Get("/logout", oidc.LogoutHandler(db, issuer)) + r.Post("/logout", oidc.LogoutHandler(db, issuer)) + + // 更新密碼(登入者自助變更):驗證目前密碼後於交易內更新並撤銷全部 + // Session,再輪替目前瀏覽器的 Session(其他裝置立即登出)。 + r.Get("/password", auth.PasswordPageHandler(db)) + r.Post("/password", auth.PasswordChangeHandler(db)) + + // OIDC 授權碼流程(RFC 6749 §4.1.1 + OIDC Core §3.1)。 + r.Get("/authorize", oidc.AuthorizeHandler(db)) + r.Post("/authorize", oidc.AuthorizeHandler(db)) + r.Post("/token", oidc.TokenHandler(db, issuer)) + r.Get("/userinfo", oidc.UserInfoHandler(db, issuer)) + r.Post("/userinfo", oidc.UserInfoHandler(db, issuer)) + + r.Get("/admin/keys", admin.KeysPageHandler(db)) + r.Post("/admin/keys", admin.KeysCreateHandler(db)) + r.Post("/admin/keys/{id}/retire", admin.KeysRetireHandler(db)) + + r.Get("/admin/applications", admin.ApplicationsPageHandler(db)) + r.Get("/admin/applications/new", admin.ApplicationNewPageHandler(db)) + r.Post("/admin/applications/new", admin.ApplicationsCreateHandler(db)) + r.Get("/admin/applications/{id}", admin.ApplicationEditPageHandler(db)) + r.Post("/admin/applications/{id}", admin.ApplicationUpdateHandler(db)) + r.Post("/admin/applications/{id}/secret", admin.ApplicationsRotateSecretHandler(db)) + r.Post("/admin/applications/{id}/delete", admin.ApplicationsDeleteHandler(db)) + + r.Handle("/static/*", auth.StaticHandler()) + + r.Get("/health", func(w http.ResponseWriter, r *http.Request) { + sqlDB, err := db.DB() + if err != nil { + w.WriteHeader(http.StatusServiceUnavailable) + w.Write([]byte("db: " + err.Error())) + return + } + if err := sqlDB.Ping(); err != nil { + w.WriteHeader(http.StatusServiceUnavailable) + w.Write([]byte("db: " + err.Error())) + return + } + w.Write([]byte("ok")) + }) + + // 未匹配任何路由的路徑(不分方法)輸出自訂 404 頁。 + r.NotFound(auth.NotFoundHandler) + + if err := http.ListenAndServe(":8080", r); err != nil { + log.Fatal(err) + } +}