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
@@ -258,7 +258,6 @@ func (a *Authenticator) fakeSalt(username string) string {
|
||||
func (a *Authenticator) VerifyLogin(username, response, nonce string) (token, role string, err error) {
|
||||
a.mu.RLock()
|
||||
u, ok := a.users[username]
|
||||
expire := a.tokenExpire
|
||||
a.mu.RUnlock()
|
||||
if !ok {
|
||||
return "", "", errors.New("invalid credentials")
|
||||
@@ -268,18 +267,30 @@ func (a *Authenticator) VerifyLogin(username, response, nonce string) (token, ro
|
||||
return "", "", errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
token, err = randomHex(tokenBytes)
|
||||
token, err = a.IssueToken(username, u.Role)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return token, u.Role, nil
|
||||
}
|
||||
|
||||
// IssueToken mints and stores a fresh session token for an identity that has
|
||||
// already been authenticated out-of-band (SSO), skipping the challenge
|
||||
// response. username is used only for audit/logging; authorization keys on
|
||||
// Role. The caller must pass a role already normalized to "admin" or "viewer".
|
||||
func (a *Authenticator) IssueToken(username, role string) (string, error) {
|
||||
token, err := randomHex(tokenBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
a.mu.Lock()
|
||||
a.tokens[token] = &Session{
|
||||
Username: username,
|
||||
Role: u.Role,
|
||||
ExpiresAt: time.Now().Add(expire),
|
||||
Role: role,
|
||||
ExpiresAt: time.Now().Add(a.tokenExpire),
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return token, u.Role, nil
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// usersFileEntry is the on-disk shape of one record in users.json. Field
|
||||
|
||||
Reference in new issue
Block a user