Neo OcLib
This commit is contained in:
+69
-74
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user