Feature: Add admin-only SSO bridge to web UI
Bridge an already-authenticated portal session into a YAMA web session via a new /sso endpoint, so portal administrators can enter the remote-desktop UI without a second login. Only the "admin" portal role is bridged; any other role (viewer, unknown) still falls back to the built-in login form, so a low-privileged portal account cannot bypass authentication. - wsauth: extract IssueToken from VerifyLogin so an out-of-band-authenticated identity (SSO) can mint a session token without the challenge/response. - web: add the SSODetector interface, a fail-closed JWT detector (HMAC-only), and the /sso handler gated to the admin role. - cmd: enable the JWT detector via YAMA_SSO_JWT_SECRET, the shared HS256 secret matching the parent portal. - web UI: attempt SSO before showing the login form, falling back to login when the server returns 401. - docs: document SSO (admin-only) and license trial-vs-paid troubleshooting in README and the env example. Co-Authored-By: deepseek-v4-pro
This commit is contained in:
1 parent
d2149851b2
commit
53b7151fb7
9 files changed
+499
-11
No files matched your search
@@ -0,0 +1,63 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// SSODetector maps an incoming HTTP request from an already-authenticated
|
||||
// portal session into a YAMA web identity (username + role). Implementations
|
||||
// are portal-specific: each one knows how to recognize its portal's session
|
||||
// credential (a cookie, a header, a JWT, ...) and translate the caller to
|
||||
// YAMA's two roles, "admin" and "viewer".
|
||||
//
|
||||
// Detect returns ok=false when the request carries no usable credential, or
|
||||
// the credential is invalid/expired; the caller then falls back to the
|
||||
// built-in login form. username is used only for audit/logging. Whether a
|
||||
// detected identity is actually bridged into a session is handleSSO's policy:
|
||||
// currently only "admin" is bridged, everyone else falls back to the login form.
|
||||
type SSODetector interface {
|
||||
Detect(r *http.Request) (username, role string, ok bool)
|
||||
}
|
||||
|
||||
// handleSSO bridges a detected portal identity into a YAMA session token.
|
||||
// Only administrators are bridged: a valid portal session for any other role
|
||||
// (viewer, unknown) still returns 401 and the front-end falls back to the
|
||||
// built-in login form, so a low-privileged portal account cannot walk straight
|
||||
// into the remote-desktop console. When no detector is configured, or the
|
||||
// request is not recognized, it also returns 401.
|
||||
func (s *Server) handleSSO(w http.ResponseWriter, r *http.Request) {
|
||||
if s.sso == nil {
|
||||
s.unauthorized(w)
|
||||
return
|
||||
}
|
||||
username, role, ok := s.sso.Detect(r)
|
||||
if !ok {
|
||||
s.unauthorized(w)
|
||||
return
|
||||
}
|
||||
// Only the "admin" role may skip the login form via SSO. Every other role
|
||||
// (viewer, unknown) must authenticate against YAMA's own login, even with
|
||||
// a valid portal session.
|
||||
if role != "admin" {
|
||||
s.unauthorized(w)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := s.auth.IssueToken(username, role)
|
||||
if err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(`{"error":"internal error"}`))
|
||||
return
|
||||
}
|
||||
|
||||
s.log.Info("sso: user=%s role=%s", username, role)
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{
|
||||
"token": token,
|
||||
"role": role,
|
||||
"username": username,
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user