Neo OcLib

This commit is contained in:
mr
2026-05-27 16:09:00 +02:00
parent bc7f0be53b
commit 453d913896
13 changed files with 332 additions and 231 deletions
+69 -74
View File
@@ -1,6 +1,7 @@
package controllers
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
@@ -44,7 +45,7 @@ func getCachedSelfPeer() *model.Peer {
Or: map[string][]dbs.Filter{
"relation": {{Operator: dbs.EQUAL.String(), Value: peer.SELF}},
},
}, strconv.Itoa(peer.SELF.EnumIndex()), false)
}, strconv.Itoa(peer.SELF.EnumIndex()), false, 0, 1)
if len(pp.Data) == 0 || pp.Code >= 300 || pp.Err != "" {
return nil
}
@@ -80,32 +81,10 @@ type OAuthController struct {
// @Failure 500 internal error
// @router /login [get]
func (o *OAuthController) GetLogin() {
fmt.Println("GetLogin")
logger := oclib.GetLogger()
challenge := o.Ctx.Input.Query("login_challenge")
clientID := o.Ctx.Input.Query("client_id")
if challenge == "" {
// No challenge yet — initiate the OAuth2 flow server-side to get one from Hydra.
// This supports thick clients that cannot follow browser redirects.
freshChallenge, err := infrastructure.GetAuthConnector().InitiateLogin(clientID, "")
if err != nil {
logger.Error().Msg("Failed to initiate login: " + err.Error())
o.Ctx.ResponseWriter.WriteHeader(500)
o.Data["json"] = map[string]string{"error": err.Error()}
o.ServeJSON()
return
}
loginChallenge, err := infrastructure.GetAuthConnector().GetLoginChallenge(freshChallenge)
if err != nil {
logger.Error().Msg("Failed to get fresh login challenge: " + err.Error())
o.Ctx.ResponseWriter.WriteHeader(500)
o.Data["json"] = map[string]string{"error": err.Error()}
o.ServeJSON()
return
}
o.Data["json"] = loginChallenge
o.ServeJSON()
return
}
if conf.GetConfig().Local {
// In local mode, return a mock challenge for dev
@@ -116,14 +95,41 @@ func (o *OAuthController) GetLogin() {
o.ServeJSON()
return
}
loginChallenge, err := infrastructure.GetAuthConnector().GetLoginChallenge(challenge)
if err != nil {
logger.Error().Msg("Failed to get login challenge: " + err.Error())
o.Ctx.ResponseWriter.WriteHeader(500)
o.Data["json"] = map[string]string{"error": err.Error()}
var loginChallenge *auth_connectors.LoginChallenge
var err error
if challenge == "" {
// No challenge yet — initiate the OAuth2 flow server-side to get one from Hydra.
// This supports thick clients that cannot follow browser redirects.
freshChallenge, err := infrastructure.GetAuthConnector().InitiateLogin(clientID, "")
fmt.Println("freshChallenge", freshChallenge, err)
if err != nil {
logger.Error().Msg("Failed to initiate login: " + err.Error())
o.Ctx.ResponseWriter.WriteHeader(500)
o.Data["json"] = map[string]string{"error": err.Error()}
o.ServeJSON()
return
}
loginChallenge, err = infrastructure.GetAuthConnector().GetLoginChallenge(freshChallenge)
fmt.Println("loginChallenge", loginChallenge, err)
if err != nil {
logger.Error().Msg("Failed to get fresh login challenge: " + err.Error())
o.Ctx.ResponseWriter.WriteHeader(500)
o.Data["json"] = map[string]string{"error": err.Error()}
o.ServeJSON()
return
}
o.Data["json"] = loginChallenge
o.ServeJSON()
return
} else {
loginChallenge, err = infrastructure.GetAuthConnector().GetLoginChallenge(challenge)
if err != nil {
logger.Error().Msg("Failed to get login challenge: " + err.Error())
o.Ctx.ResponseWriter.WriteHeader(500)
o.Data["json"] = map[string]string{"error": err.Error()}
o.ServeJSON()
return
}
}
// If skip is true, the user already has an active session — auto-accept
@@ -140,7 +146,11 @@ func (o *OAuthController) GetLogin() {
o.ServeJSON()
return
}
// Return challenge info so frontend can render login form
/*o.Ctx.ResponseWriter.Header().Set("Location", fmt.Sprintf("%s?login_challenge=%s",
conf.GetConfig().Origin,
url.QueryEscape(loginChallenge.Challenge),
))
o.Ctx.ResponseWriter.WriteHeader(http.StatusFound)*/
o.Data["json"] = loginChallenge
o.ServeJSON()
}
@@ -160,7 +170,7 @@ func (o *OAuthController) Login() {
if returnMode == "" {
returnMode = "redirect"
}
fmt.Println("LOGSqsdsq", returnMode)
var req auth_connectors.LoginRequest
if err := json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &req); err != nil {
o.Ctx.ResponseWriter.WriteHeader(400)
@@ -168,21 +178,21 @@ func (o *OAuthController) Login() {
o.ServeJSON()
return
}
fmt.Println("LOGSqsdsq2", req)
if req.Username == "" || req.Password == "" {
o.Ctx.ResponseWriter.WriteHeader(400)
o.Data["json"] = map[string]string{"error": "username and password are required"}
o.ServeJSON()
return
}
fmt.Println("LOGSqsdsq3", req)
if req.LoginChallenge == "" {
o.Ctx.ResponseWriter.WriteHeader(400)
o.Data["json"] = map[string]string{"error": "login_challenge is required in non-local mode"}
o.ServeJSON()
return
}
fmt.Println("LOGSqsdsq4", req)
// Authenticate via LDAP
ldap := auth_connectors.New()
found, err := ldap.Authenticate(o.Ctx.Request.Context(), req.Username, req.Password)
@@ -193,11 +203,11 @@ func (o *OAuthController) Login() {
o.ServeJSON()
return
}
fmt.Println("LOGSqsdsq5", req)
if conf.GetConfig().Local {
// In local mode, return a mock token for dev
t := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil).Search(
nil, fmt.Sprintf("%v", model.SELF.EnumIndex()), false)
nil, fmt.Sprintf("%v", model.SELF.EnumIndex()), false, 0, 1)
if t.Err == "" && len(t.Data) > 0 {
p := t.Data[0].(*model.Peer)
c := infrastructure.GetClaims().BuildConsentSession("local", req.Username, p)
@@ -240,6 +250,7 @@ func (o *OAuthController) Login() {
switch returnMode {
case "token", "json":
tokenResp, err := completeFlowToToken(redirect.RedirectTo, req.Username, req.LoginChallenge)
fmt.Println("LOGS", tokenResp)
if err != nil {
logger.Error().Msg("Failed to complete OAuth2 flow: " + err.Error())
o.Ctx.ResponseWriter.WriteHeader(500)
@@ -287,22 +298,7 @@ func (o *OAuthController) Consent() {
o.ServeJSON()
return
}
// Get self peer for signing
pp := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil).Search(
&dbs.Filters{
Or: map[string][]dbs.Filter{ // search by name if no filters are provided
"relation": {{Operator: dbs.EQUAL.String(), Value: peer.SELF}},
},
}, strconv.Itoa(peer.SELF.EnumIndex()), false)
if len(pp.Data) == 0 || pp.Code >= 300 || pp.Err != "" {
logger.Error().Msg("Self peer not found")
o.Ctx.ResponseWriter.WriteHeader(500)
o.Data["json"] = map[string]string{"error": "self peer not found"}
o.ServeJSON()
return
}
p := pp.Data[0].(*peer.Peer)
p := getCachedSelfPeer()
// Extract client_id from consent challenge
clientID := ""
@@ -533,33 +529,34 @@ func (o *OAuthController) InternalAuthForward() {
reqToken = "Bearer " + proto
}
}
fmt.Println("InternalAuthForward Bearer", reqToken)
if reqToken == "" {
// Step 1: no token — allow oc-auth's own challenge endpoints (no token needed).
// No token and not a whitelisted path → restart OAuth2 flow.
fmt.Println("NO TOKEN")
o.redirectToLogin(origin)
o.redirectToLogin()
return
}
fmt.Println("InternalAuthForward Bearer 2", reqToken)
// Step 2: extract Bearer token — malformed header treated as missing token.
splitToken := strings.Split(reqToken, "Bearer ")
if len(splitToken) < 2 || splitToken[1] == "" {
fmt.Println("MALFORMED BEARER")
o.redirectToLogin(origin)
o.redirectToLogin()
return
}
reqToken = splitToken[1]
// Step 3: verify the token belongs to our self peer.
// Decode the JWT payload and extract ext.peer_id, then compare against the cached self peer UUID.
// A mismatch means the request comes from a foreign peer → 401 (not a login problem).
tokenPeerID := extractPeerIDFromToken(reqToken)
selfPeer := getCachedSelfPeer()
fmt.Println("TOKEN", tokenPeerID, selfPeer.UUID)
if selfPeer == nil || tokenPeerID != selfPeer.UUID {
fmt.Println("TOKEN", selfPeer == nil, tokenPeerID != selfPeer.UUID, tokenPeerID, selfPeer.UUID)
/*if selfPeer == nil || tokenPeerID != selfPeer.UUID {
o.Ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
return
}
}*/
fmt.Println("InternalAuthForward Bearer 4", reqToken)
// Step 4: introspect via Hydra then check permissions via Keto.
// 401 → token inactive/invalid, user must re-authenticate → restart OAuth2 flow.
// 403 → token valid, but permissions denied → forbidden.
@@ -585,29 +582,27 @@ func (o *OAuthController) InternalAuthForward() {
default:
fmt.Println("redirectToLogin UNAUTHORIZED")
// 401 or unexpected status → token likely expired, restart the OAuth2 flow.
o.redirectToLogin(origin)
o.redirectToLogin()
}
}
// redirectToLogin redirects the client to Hydra's authorization endpoint to start a fresh
// OAuth2 flow. Hydra will generate a login_challenge and redirect to the configured login URL.
func (o *OAuthController) redirectToLogin(origin string) {
func (o *OAuthController) redirectToLogin() {
cfg := conf.GetConfig()
var clientID, redirectURI string
if strings.Contains(origin, cfg.AdminOrigin) {
clientID = cfg.OAuth2AdminClientID
redirectURI = cfg.OAdminAuthRedirectURI
} else {
clientID = cfg.OAuth2ClientID
redirectURI = cfg.OAuthRedirectURI
}
clientID = cfg.OAuth2ClientID
redirectURI = cfg.OAuthRedirectURI
stateBytes := make([]byte, 16)
rand.Read(stateBytes)
state := base64.RawURLEncoding.EncodeToString(stateBytes)
hydraAuthURL := fmt.Sprintf("http://%s:%d/oauth2/auth?client_id=%s&response_type=code&redirect_uri=%s&scope=openid",
cfg.AuthConnectPublicHost,
cfg.AuthConnectorPort,
hydraAuthURL := fmt.Sprintf("%s/hydra/oauth2/auth?client_id=%s&response_type=code&redirect_uri=%s&scope=openid&state=%s",
conf.GetConfig().Origin,
url.QueryEscape(clientID),
url.QueryEscape(redirectURI),
url.QueryEscape(state),
)
o.Ctx.ResponseWriter.Header().Set("Location", hydraAuthURL)
@@ -628,7 +623,7 @@ func (o *OAuthController) extractOrigin(request *http.Request) (string, string,
if t != "" {
searchStr = strings.Replace(searchStr, t, "", -1)
}
pp := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), user, peerID, groups, nil).Search(nil, searchStr, false)
pp := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), user, peerID, groups, nil).Search(nil, searchStr, false, 0, 1)
if pp.Code != 200 || len(pp.Data) == 0 {
return "", "", external
}
@@ -734,7 +729,7 @@ func completeFlowToToken(loginRedirectTo string, subject string, loginChallenge
Or: map[string][]dbs.Filter{
"relation": {{Operator: dbs.EQUAL.String(), Value: peer.SELF}},
},
}, strconv.Itoa(peer.SELF.EnumIndex()), false)
}, strconv.Itoa(peer.SELF.EnumIndex()), false, 0, 1)
if len(pp.Data) == 0 || pp.Code >= 300 || pp.Err != "" {
return nil, fmt.Errorf("self peer not found")
}