| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- package identity
- /*
- ArozOS Cluster - identity endpoints
- Node-facing (signed, served only by the identity origin):
- POST /cluster/acn/auth/verify check a username + password hash
- GET /cluster/acn/auth/directory full account directory
- POST /cluster/acn/auth/setpassword password change forwarded by a member
- Admin-facing (/system/cluster/identity/*, mounted admin-only by the core):
- status, origin, sync
- */
- import (
- "encoding/json"
- "net/http"
- "strings"
- "imuslab.com/arozos/mod/cluster/acn"
- "imuslab.com/arozos/mod/utils"
- )
- func (i *Manager) registerACNHandlers() {
- srv := i.m.Server()
- srv.HandleFunc(acn.BasePath+"/auth/verify", i.handleVerify)
- srv.HandleFunc(acn.BasePath+"/auth/directory", i.handleDirectory)
- srv.HandleFunc(acn.BasePath+"/auth/setpassword", i.handleSetPassword)
- }
- // requireOrigin rejects node requests when this node is not the identity origin.
- func (i *Manager) requireOrigin(w http.ResponseWriter) bool {
- if _, self := i.Origin(); !self {
- acn.WriteError(w, http.StatusConflict, "this node is not the identity origin of the cluster")
- return false
- }
- return true
- }
- func (i *Manager) handleVerify(w http.ResponseWriter, r *http.Request, sender *acn.SignedIdentity, body []byte) {
- if !i.requireOrigin(w) {
- return
- }
- var req VerifyRequest
- if err := json.Unmarshal(body, &req); err != nil || !validUsername(req.Username) || req.PasswordHash == "" {
- acn.WriteJSON(w, VerifyResponse{OK: false, Reason: "Invalid username or password"})
- return
- }
- stored, err := i.acc.PasswordHash(req.Username)
- if err != nil || stored == "" || !constantTimeEqual(stored, req.PasswordHash) {
- acn.WriteJSON(w, VerifyResponse{OK: false, Reason: "Invalid username or password"})
- return
- }
- groups, _ := i.acc.Groups(req.Username)
- if groups == nil {
- groups = []string{}
- }
- acn.WriteJSON(w, VerifyResponse{OK: true, Groups: groups})
- }
- func (i *Manager) handleDirectory(w http.ResponseWriter, r *http.Request, sender *acn.SignedIdentity, body []byte) {
- if !i.requireOrigin(w) {
- return
- }
- acn.WriteJSON(w, i.buildDirectory())
- }
- func (i *Manager) handleSetPassword(w http.ResponseWriter, r *http.Request, sender *acn.SignedIdentity, body []byte) {
- if !i.requireOrigin(w) {
- return
- }
- var req SetPasswordRequest
- if err := json.Unmarshal(body, &req); err != nil || !validUsername(req.Username) || req.PasswordHash == "" {
- acn.WriteError(w, http.StatusBadRequest, "invalid request")
- return
- }
- if !i.acc.UserExists(req.Username) {
- acn.WriteError(w, http.StatusNotFound, "user not found on the identity origin")
- return
- }
- if err := i.acc.SetPasswordHash(req.Username, req.PasswordHash); err != nil {
- acn.WriteError(w, http.StatusInternalServerError, err.Error())
- return
- }
- acn.WriteJSON(w, map[string]bool{"ok": true})
- }
- func constantTimeEqual(a string, b string) bool {
- if len(a) != len(b) {
- return false
- }
- var diff byte
- for k := 0; k < len(a); k++ {
- diff |= a[k] ^ b[k]
- }
- return diff == 0
- }
- /*
- Admin handlers
- */
- // HandleStatus returns the identity status.
- func (i *Manager) HandleStatus(w http.ResponseWriter, r *http.Request) {
- js, _ := json.Marshal(i.Status())
- utils.SendJSONResponse(w, string(js))
- }
- // HandleOrigin sets the identity origin: id=self, id=<node id>, or id= (disable).
- func (i *Manager) HandleOrigin(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodPost {
- utils.SendErrorResponse(w, "POST required")
- return
- }
- id := strings.TrimSpace(r.PostFormValue("id"))
- if id == "self" {
- id = i.m.NodeID()
- }
- if err := i.m.SetIdentityOrigin(id); err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- utils.SendOK(w)
- }
- // HandleSync pulls the origin directory immediately.
- func (i *Manager) HandleSync(w http.ResponseWriter, r *http.Request) {
- if err := i.SyncNow(); err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- js, _ := json.Marshal(i.Status())
- utils.SendJSONResponse(w, string(js))
- }
- // RegisterAdminRoutes mounts the admin handlers on the given register function.
- func (i *Manager) RegisterAdminRoutes(register func(pattern string, handler func(http.ResponseWriter, *http.Request))) {
- register("/system/cluster/identity/status", i.HandleStatus)
- register("/system/cluster/identity/origin", i.HandleOrigin)
- register("/system/cluster/identity/sync", i.HandleSync)
- }
|