Author | Jakob Wakeling <[email protected]> |
Date | 2025-01-03 02:33:07 |
Commit | 44175afe9872652f92cb0700b3bc59affb2393bb |
Parent | 6c2749bbdf5760923827884196ad10a2a0b3388a |
Implement user SSH key management
Diffstat
M | res/res.go | | | 6 | ++++++ |
M | res/user/header.html | | | 2 | ++ |
A | res/user/keys.html | | | 35 | +++++++++++++++++++++++++++++++++++ |
A | res/user/keys_add.html | | | 22 | ++++++++++++++++++++++ |
M | src/goit/auth.go | | | 4 | ++-- |
M | src/goit/auth_test.go | | | 2 | +- |
M | src/goit/config.go | | | 28 | +++++++++++++++------------- |
M | src/goit/db.go | | | 49 | +++++++++++++++++++++++++++++++++++++++++-------- |
M | src/goit/git.go | | | 5 | ----- |
M | src/goit/goit.go | | | 16 | ++++++++++++++-- |
M | src/goit/http.go | | | 4 | +++- |
A | src/goit/keys.go | | | 84 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/goit/ssh.go | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/main.go | | | 12 | ++++++++---- |
M | src/repo/commit.go | | | 1 | + |
M | src/repo/create.go | | | 1 | + |
M | src/repo/file.go | | | 1 | + |
M | src/repo/refs.go | | | 1 | + |
M | src/repo/repo.go | | | 2 | +- |
M | src/repo/tree.go | | | 1 | + |
M | src/user/edit.go | | | 1 | + |
A | src/user/keys.go | | | 137 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/user/login.go | | | 2 | ++ |
M | src/user/sessions.go | | | 1 | + |
24 files changed, 445 insertions, 37 deletions
diff --git a/res/res.go b/res/res.go index 772f4e6..23d3ace 100644 --- a/res/res.go +++ b/res/res.go @@ -49,6 +49,12 @@ var UserSessions string //go:embed user/edit.html var UserEdit string +//go:embed user/keys.html +var UserKeys string + +//go:embed user/keys_add.html +var UserKeysAdd string + //go:embed repo/header.html var RepoHeader string diff --git a/res/user/header.html b/res/user/header.html index a984290..27e2c8d 100644 --- a/res/user/header.html +++ b/res/user/header.html @@ -10,6 +10,8 @@ <td> <a href="/user/sessions">Sessions</a> | <a href="/user/edit">Edit</a> + | <a href="/user/keys">Keys</a> + | <a href="/user/keys/add">Add Key</a> </td> </tr> </table> diff --git a/res/user/keys.html b/res/user/keys.html new file mode 100644 index 0000000..528d785 --- /dev/null +++ b/res/user/keys.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html lang="en"> + <head>{{template "base/head" .}}</head> + <body> + <header>{{template "user/header" .}}</header><hr> + <main> + <table> + <thead> + <tr> + <td><b>Type</b></td> + <td><b>Description</b></td> + <td><b>Key</b></td> + <td></td> + </tr> + </thead> + <tbody> + {{range $i, $key := .Keys}} + <tr> + <td>{{.Type}}</td> + <td>{{.Description}}</td> + <td>{{index $.KeyLines $i}}</td> + <td> + <form action="/user/keys" method="post"> + {{$.CSRFField}} + <input type="hidden" name="kid" value="{{.ID}}"> + <input type="submit" name="submit" value="Delete"> + </form> + </td> + </tr> + {{end}} + </tbody> + </table> + </main> + </body> +</html> diff --git a/res/user/keys_add.html b/res/user/keys_add.html new file mode 100644 index 0000000..3be1308 --- /dev/null +++ b/res/user/keys_add.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> + <head>{{template "base/head" .}}</head> + <body> + <header>{{template "user/header" .}}</header><hr> + <main> + <form action="/user/keys/add" method="post"> + {{.CSRFField}} + <table> + <tr><td><label for="key">SSH Public Key</label></td></tr> + <tr><td><textarea name="key" spellcheck="false">{{.Form.Key}}</textarea></td></tr> + <tr> + <td> + <input type="submit" name="submit" value="Add"> + <span style="color: #AA0000">{{.Message}}</span> + </td> + </tr> + </table> + </form> + </main> + </body> +</html> diff --git a/src/goit/auth.go b/src/goit/auth.go index 41832d4..ceece02 100644 --- a/src/goit/auth.go +++ b/src/goit/auth.go @@ -34,7 +34,7 @@ func NewSession(uid int64, ip string, expiry time.Time) (Session, error) { } var t = base64.StdEncoding.EncodeToString(b) - var s = Session{Token: t, Ip: util.If(Conf.IpSessions, ip, ""), Seen: time.Now(), Expiry: expiry} + var s = Session{Token: t, Ip: util.If(Conf.IPSessions, ip, ""), Seen: time.Now(), Expiry: expiry} SessionsMutex.Lock() util.Debugln("[goit.NewSession] SessionsMutex lock") @@ -111,7 +111,7 @@ func CleanupSessions() { func SetSessionCookie(w http.ResponseWriter, uid int64, s Session) { c := &http.Cookie{ Name: "session", Value: fmt.Sprint(uid) + "." + s.Token, Path: "/", Expires: s.Expiry, - Secure: util.If(Conf.UsesHttps, true, false), HttpOnly: true, SameSite: http.SameSiteLaxMode, + Secure: util.If(Conf.UsesHTTPS, true, false), HttpOnly: true, SameSite: http.SameSiteLaxMode, } if err := c.Valid(); err != nil { diff --git a/src/goit/auth_test.go b/src/goit/auth_test.go index 3b39c71..3a091ca 100644 --- a/src/goit/auth_test.go +++ b/src/goit/auth_test.go @@ -20,7 +20,7 @@ func TestNewSession(t *testing.T) { var uid int64 = 1 var session = goit.Session{Ip: "127.0.0.1", Expiry: time.Unix(0, 0)} - goit.Conf.IpSessions = true + goit.Conf.IPSessions = true s, err := goit.NewSession(uid, session.Ip, session.Expiry) if err != nil { t.Fatal(err.Error()) diff --git a/src/goit/config.go b/src/goit/config.go index df8d4a8..9e29c64 100644 --- a/src/goit/config.go +++ b/src/goit/config.go @@ -14,27 +14,29 @@ type config struct { DataPath string `json:"data_path"` LogsPath string `json:"logs_path"` RuntimePath string `json:"runtime_path"` - HttpAddr string `json:"http_addr"` - HttpPort string `json:"http_port"` + HTTPAddr string `json:"http_addr"` + HTTPPort string `json:"http_port"` GitPath string `json:"git_path"` - IpSessions bool `json:"ip_sessions"` - UsesHttps bool `json:"uses_https"` - IpForwarded bool `json:"ip_forwarded"` - CsrfSecret string `json:"csrf_secret"` + IPSessions bool `json:"ip_sessions"` + UsesHTTPS bool `json:"uses_https"` + IPForwarded bool `json:"ip_forwarded"` + EnableSSH bool `json:"enable_ssh"` + CSRFSecret string `json:"csrf_secret"` } -func loadConfig() (config, error) { +func LoadConfig() (config, error) { conf := config{ DataPath: dataPath(), LogsPath: logsPath(), RuntimePath: runtimePath(), - HttpAddr: "", - HttpPort: "8080", + HTTPAddr: "", + HTTPPort: "8080", GitPath: "git", - IpSessions: true, - UsesHttps: false, - IpForwarded: false, - CsrfSecret: "1234567890abcdef1234567890abcdef", + IPSessions: true, + UsesHTTPS: false, + IPForwarded: false, + EnableSSH: false, + CSRFSecret: "1234567890abcdef1234567890abcdef", } /* Load config file(s) */ diff --git a/src/goit/db.go b/src/goit/db.go index cdc24e4..f3a0516 100644 --- a/src/goit/db.go +++ b/src/goit/db.go @@ -18,6 +18,7 @@ users: pass_algo TEXT NOT NULL salt BLOB NOT NULL is_admin BOOLEAN NOT NULL + default_visibility INTEGER NOT NULL DEFAULT 0 repos: id INTEGER PRIMARY KEY AUTOINCREMENT @@ -29,11 +30,18 @@ repos: upstream TEXT NOT NULL visibility INTEGER NOT NULL DEFAULT 0 is_mirror BOOLEAN NOT NULL + +keys: + id INTEGER PRIMARY KEY AUTOINCREMENT + owner_id INTEGER NOT NULL + type INTEGER NOT NULL + description TEXT NOT NULL + key BLOB NOT NULL */ -func updateDatabase(db *sql.DB) error { - const LATEST_VERSION = 3 +const LATEST_VERSION = 4 +func updateDatabase(db *sql.DB) error { tx, err := db.Begin() if err != nil { return err @@ -51,8 +59,8 @@ func updateDatabase(db *sql.DB) error { logMigration := true - if version <= 0 { - /* Database is empty or new, initialise from scratch */ + if version < 1 { + /* Database is empty or new, initialise from scratch. */ log.Println("Initialising database at version", LATEST_VERSION) logMigration = false @@ -87,7 +95,7 @@ func updateDatabase(db *sql.DB) error { version = 1 } - if version <= 1 { + if version < 2 { if logMigration { log.Println("Migrating database from version 1 to 2") } @@ -98,7 +106,7 @@ func updateDatabase(db *sql.DB) error { version = 2 } - if version <= 2 { + if version < 3 { if logMigration { log.Println("Migrating database from version 2 to 3") } @@ -107,7 +115,7 @@ func updateDatabase(db *sql.DB) error { return err } - /* Set values for each repo according to is_private */ + /* Set values for each repo according to is_private. */ var visibilities = map[int64]Visibility{} if rows, err := tx.Query("SELECT id, is_private FROM repos"); err != nil { @@ -133,13 +141,38 @@ func updateDatabase(db *sql.DB) error { } } - /* Remove is_private column */ + /* Remove is_private column. */ if _, err := tx.Exec("ALTER TABLE repos DROP COLUMN is_private"); err != nil { return err } version = 3 } + if version < 4 { + if logMigration { + log.Println("Migrating database from version 3 to 4") + } + + /* Add the default_visibility column to users. */ + if _, err := tx.Exec("ALTER TABLE users ADD COLUMN default_visibility INTEGER NOT NULL DEFAULT 0"); err != nil { + return err + } + + /* Create the keys table. */ + if _, err := tx.Exec( + `CREATE TABLE IF NOT EXISTS keys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + owner_id INTEGER NOT NULL, + type INTEGER NOT NULL, + description TEXT NOT NULL, + key BLOB NOT NULL + )`, + ); err != nil { + return err + } + + version = 4 + } if _, err := tx.Exec(fmt.Sprint("PRAGMA user_version = ", version)); err != nil { return err diff --git a/src/goit/git.go b/src/goit/git.go index 101eb8a..0c93c50 100644 --- a/src/goit/git.go +++ b/src/goit/git.go @@ -132,11 +132,6 @@ func gitHttpBase(w http.ResponseWriter, r *http.Request, service string) *Repo { } } - if repo == nil { - w.WriteHeader(http.StatusNotFound) - return nil - } - return repo } diff --git a/src/goit/goit.go b/src/goit/goit.go index 050bb54..98e3d08 100644 --- a/src/goit/goit.go +++ b/src/goit/goit.go @@ -36,7 +36,7 @@ var Reserved []string = []string{"admin", "repo", "static", "user"} var StartTime = time.Now() func Goit() error { - if conf, err := loadConfig(); err != nil { + if conf, err := LoadConfig(); err != nil { return err } else { Conf = conf @@ -54,6 +54,10 @@ func Goit() error { log.SetOutput(io.MultiWriter(os.Stderr, logFile)) log.Println("Starting Goit", res.Version) + if Conf.CSRFSecret == "1234567890abcdef1234567890abcdef" { + log.Println("[config] WARNING: CSRF secret is insecure") + } + log.Println("[Config] using data path:", Conf.DataPath) if err := os.MkdirAll(Conf.DataPath, 0o777); err != nil { return fmt.Errorf("[config] %w", err) @@ -91,6 +95,11 @@ func Goit() error { } } + /* Trigger an SSH authorized keys file update. */ + if err := UpdateAuthorizedKeys(); err != nil { + return err + } + /* Initialise and start the cron service */ Cron = cron.New() Cron.Start() @@ -123,6 +132,9 @@ func Goit() error { return nil } +/* Set the internal db variable, ONLY used by goit-shell. */ +func ShellSetDB(d *sql.DB) { db = d } + func RepoPath(name string, abs bool) string { return util.If(abs, filepath.Join(Conf.DataPath, "repos", name+".git"), filepath.Join(name+".git")) } @@ -138,7 +150,7 @@ func IsLegal(s string) bool { } func Backup() error { - if conf, err := loadConfig(); err != nil { + if conf, err := LoadConfig(); err != nil { return err } else { Conf = conf diff --git a/src/goit/http.go b/src/goit/http.go index 62f81ed..2fbddc8 100644 --- a/src/goit/http.go +++ b/src/goit/http.go @@ -31,6 +31,8 @@ func init() { template.Must(Tmpl.New("user/login").Parse(res.UserLogin)) template.Must(Tmpl.New("user/sessions").Parse(res.UserSessions)) template.Must(Tmpl.New("user/edit").Parse(res.UserEdit)) + template.Must(Tmpl.New("user/keys").Parse(res.UserKeys)) + template.Must(Tmpl.New("user/keys/add").Parse(res.UserKeysAdd)) template.Must(Tmpl.New("repo/header").Parse(res.RepoHeader)) template.Must(Tmpl.New("repo/create").Parse(res.RepoCreate)) @@ -55,7 +57,7 @@ func HttpNotFound(w http.ResponseWriter, r *http.Request) { } func Ip(r *http.Request) string { - if fip := r.Header.Get("X-Forwarded-For"); Conf.IpForwarded && fip != "" { + if fip := r.Header.Get("X-Forwarded-For"); Conf.IPForwarded && fip != "" { return fip } diff --git a/src/goit/keys.go b/src/goit/keys.go new file mode 100644 index 0000000..bb7c37c --- /dev/null +++ b/src/goit/keys.go @@ -0,0 +1,84 @@ +// Copyright (C) 2025, Jakob Wakeling +// All rights reserved. + +package goit + +type Key struct { + ID int64 `json:"id"` + OwnerID int64 `json:"owner_id"` + Type KeyType `json:"type"` + Description string `json:"description"` + Key []byte `json:"key"` +} + +type KeyType int32 + +const ( + SSH_Auth KeyType = 0 +) + +func KeyTypeFromString(s string) KeyType { + switch s { + case "ssh-auth": + return SSH_Auth + default: + return -1 + } +} + +func (t KeyType) String() string { + return [...]string{"ssh-auth"}[t] +} + +func GetKeys(uid int64) ([]Key, error) { + keys := []Key{} + + rows, err := db.Query("SELECT id, owner_id, type, description, key FROM keys WHERE owner_id = ?", uid) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + k := Key{} + if err := rows.Scan(&k.ID, &k.OwnerID, &k.Type, &k.Description, &k.Key); err != nil { + return nil, err + } + + keys = append(keys, k) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return keys, nil +} + +/* Input key ID is ignored. */ +func AddKey(key Key) error { + if _, err := db.Exec( + "INSERT INTO keys (owner_id, type, description, key) VALUES (?, ?, ?, ?)", + key.OwnerID, key.Type, key.Description, key.Key, + ); err != nil { + return err + } + + if err := UpdateAuthorizedKeys(); err != nil { + return err + } + + return nil +} + +func DelKey(kid int64) error { + if _, err := db.Exec("DELETE FROM keys WHERE id = ?", kid); err != nil { + return err + } + + if err := UpdateAuthorizedKeys(); err != nil { + return err + } + + return nil +} diff --git a/src/goit/ssh.go b/src/goit/ssh.go new file mode 100644 index 0000000..5de11d0 --- /dev/null +++ b/src/goit/ssh.go @@ -0,0 +1,65 @@ +// Copyright (C) 2025, Jakob Wakeling +// All rights reserved. + +package goit + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/Jamozed/Goit/src/util" + "golang.org/x/crypto/ssh" +) + +func UpdateAuthorizedKeys() error { + if !Conf.EnableSSH { + return nil + } + + log.Println("Updating SSH authorized keys file") + + f, err := os.Create(filepath.Join(os.Getenv("HOME"), ".ssh", "authorized_keys")) + if err != nil { + return err + } + defer f.Close() + + f.WriteString("# This file is managed by Goit; edits will be overwritten.\n") + + /* Write each users SSH keys to the SSH authorized keys file. */ + users, err := GetUsers() + if err != nil { + return err + } + + for _, u := range users { + keys, err := GetKeys(u.Id) + if err != nil { + util.PrintFuncError(err) + continue + } + + for _, k := range keys { + if k.Type != SSH_Auth { + continue + } + + ks, err := ssh.ParsePublicKey(k.Key) + if err != nil { + util.PrintFuncError(err) + continue + } + + if _, err := f.WriteString( + fmt.Sprintf("command=\"goit-shell %s\" %s", u.Name, string(ssh.MarshalAuthorizedKey(ks))), + ); err != nil { + util.PrintFuncError(err) + continue + } + } + } + + return nil +} diff --git a/src/main.go b/src/main.go index 6af8707..e47d18d 100644 --- a/src/main.go +++ b/src/main.go @@ -72,8 +72,8 @@ func main() { }) protect = csrf.Protect( - []byte(goit.Conf.CsrfSecret), csrf.FieldName("csrf.Token"), csrf.CookieName("csrf"), - csrf.Secure(util.If(goit.Conf.UsesHttps, true, false)), + []byte(goit.Conf.CSRFSecret), csrf.FieldName("csrf.Token"), csrf.CookieName("csrf"), + csrf.Secure(util.If(goit.Conf.UsesHTTPS, true, false)), ) h.Group(func(r chi.Router) { @@ -86,6 +86,10 @@ func main() { r.Post("/user/logout", goit.HandleUserLogout) r.Get("/user/sessions", user.HandleSessions) r.Post("/user/sessions", user.HandleSessions) + r.Get("/user/keys", user.HandleKeys) + r.Post("/user/keys", user.HandleKeys) + r.Get("/user/keys/add", user.HandleKeysAdd) + r.Post("/user/keys/add", user.HandleKeysAdd) r.Get("/user/edit", user.HandleEdit) r.Post("/user/edit", user.HandleEdit) r.Get("/repo/create", repo.HandleCreate) @@ -131,7 +135,7 @@ func main() { // h.Post("/{repo}/git-receive-pack", goit.HandleReceivePack) /* Listen for HTTP on the specified port */ - if err := http.ListenAndServe(goit.Conf.HttpAddr+":"+goit.Conf.HttpPort, h); err != nil { + if err := http.ListenAndServe(goit.Conf.HTTPAddr+":"+goit.Conf.HTTPPort, h); err != nil { log.Fatalln("[http]", err.Error()) } } @@ -142,7 +146,7 @@ func logHttp(next http.Handler) http.Handler { next.ServeHTTP(w, r) ip := r.RemoteAddr - if fip := r.Header.Get("X-Forwarded-For"); goit.Conf.IpForwarded && fip != "" { + if fip := r.Header.Get("X-Forwarded-For"); goit.Conf.IPForwarded && fip != "" { ip = fip } diff --git a/src/repo/commit.go b/src/repo/commit.go index 4dd0e79..423791e 100644 --- a/src/repo/commit.go +++ b/src/repo/commit.go @@ -26,6 +26,7 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println("[/repo/commit]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) + return } repo, err := goit.GetRepoByName(chi.URLParam(r, "repo")) diff --git a/src/repo/create.go b/src/repo/create.go index 9774fe3..96e2456 100644 --- a/src/repo/create.go +++ b/src/repo/create.go @@ -21,6 +21,7 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println("[admin]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) + return } if !auth { diff --git a/src/repo/file.go b/src/repo/file.go index 161573e..e0935fb 100644 --- a/src/repo/file.go +++ b/src/repo/file.go @@ -25,6 +25,7 @@ func HandleFile(w http.ResponseWriter, r *http.Request) { if err != nil { util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) + return } tpath := chi.URLParam(r, "*") diff --git a/src/repo/refs.go b/src/repo/refs.go index 024c40a..5f87bc5 100644 --- a/src/repo/refs.go +++ b/src/repo/refs.go @@ -24,6 +24,7 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println("[admin]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) + return } repo, err := goit.GetRepoByName(chi.URLParam(r, "repo")) diff --git a/src/repo/repo.go b/src/repo/repo.go index fa38468..a35398a 100644 --- a/src/repo/repo.go +++ b/src/repo/repo.go @@ -21,7 +21,7 @@ type HeaderFields struct { func GetHeaderFields(auth bool, user *goit.User, repo *goit.Repo, host string) HeaderFields { return HeaderFields{ Name: repo.Name, Description: repo.Description, - Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + host + "/" + repo.Name, + Url: util.If(goit.Conf.UsesHTTPS, "https://", "http://") + host + "/" + repo.Name, Editable: (auth && repo.OwnerId == user.Id), Mirror: util.If(repo.IsMirror, repo.Upstream, ""), } diff --git a/src/repo/tree.go b/src/repo/tree.go index 3a14e26..a3febfa 100644 --- a/src/repo/tree.go +++ b/src/repo/tree.go @@ -26,6 +26,7 @@ func HandleTree(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println("[admin]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) + return } tpath := chi.URLParam(r, "*") diff --git a/src/user/edit.go b/src/user/edit.go index 8468b95..6d2f347 100644 --- a/src/user/edit.go +++ b/src/user/edit.go @@ -20,6 +20,7 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println("[admin]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) + return } if !auth { diff --git a/src/user/keys.go b/src/user/keys.go new file mode 100644 index 0000000..91845ee --- /dev/null +++ b/src/user/keys.go @@ -0,0 +1,137 @@ +// Copyright (C) 2025, Jakob Wakeling +// All rights reserved. + +package user + +import ( + "fmt" + "html/template" + "net/http" + "strconv" + "strings" + + "github.com/Jamozed/Goit/src/goit" + "github.com/Jamozed/Goit/src/util" + "github.com/gorilla/csrf" + "golang.org/x/crypto/ssh" +) + +func HandleKeys(w http.ResponseWriter, r *http.Request) { + auth, user, err := goit.Auth(w, r, true) + if err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + if !auth { + goit.HttpError(w, http.StatusUnauthorized) + return + } + + data := struct { + Title string + Keys []goit.Key + KeyLines []string + CSRFField template.HTML + }{ + Title: "User - Keys", + CSRFField: csrf.TemplateField(r), + } + + if r.Method == http.MethodPost { + fmt.Println(r.FormValue("submit")) + if r.FormValue("submit") == "Delete" { + kid, err := strconv.ParseInt(r.FormValue("kid"), 10, 64) + if err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + if err := goit.DelKey(kid); err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + /* Redirect to user keys page on success. */ + http.Redirect(w, r, "/user/keys", http.StatusFound) + return + } + } + + if keys, err := goit.GetKeys(user.Id); err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } else { + data.Keys = keys + } + + for _, key := range data.Keys { + k, err := ssh.ParsePublicKey(key.Key) + if err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + data.KeyLines = append(data.KeyLines, strings.TrimSuffix(string(ssh.MarshalAuthorizedKey(k)), "\n")) + } + + if err := goit.Tmpl.ExecuteTemplate(w, "user/keys", data); err != nil { + util.PrintFuncError(err) + } +} + +func HandleKeysAdd(w http.ResponseWriter, r *http.Request) { + auth, user, err := goit.Auth(w, r, true) + if err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + if !auth { + goit.HttpError(w, http.StatusUnauthorized) + return + } + + data := struct { + Title, Message string + + Form struct{ Key string } + CSRFField template.HTML + }{ + Title: "User - Add Key", + CSRFField: csrf.TemplateField(r), + } + + if r.Method == http.MethodPost { + data.Form.Key = r.FormValue("key") + + if data.Form.Key == "" { + data.Message = "Key cannot be empty" + } else if key, comment, options, _, err := ssh.ParseAuthorizedKey([]byte(data.Form.Key)); err != nil { + data.Message = "Invalid SSH public key" + } else if len(options) != 0 { + data.Message = "Key options are not permitted" + } else if comment == "" { + data.Message = "Key comment is required" + } else if err := goit.AddKey(goit.Key{ + OwnerID: user.Id, Description: comment, Key: key.Marshal(), Type: goit.SSH_Auth, + }); err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } else { + /* Redirect to user keys page on success. */ + http.Redirect(w, r, "/user/keys", http.StatusFound) + return + } + } + + if err := goit.Tmpl.ExecuteTemplate(w, "user/keys/add", data); err != nil { + util.PrintFuncError(err) + } +} diff --git a/src/user/login.go b/src/user/login.go index aa81c61..ba727f3 100644 --- a/src/user/login.go +++ b/src/user/login.go @@ -19,10 +19,12 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println("[admin]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) + return } if auth { http.Redirect(w, r, "/", http.StatusFound) + return } data := struct { diff --git a/src/user/sessions.go b/src/user/sessions.go index 6526cc9..9d2896e 100644 --- a/src/user/sessions.go +++ b/src/user/sessions.go @@ -19,6 +19,7 @@ func HandleSessions(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println("[admin]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) + return } if !auth {