mirror of
https://github.com/1f349/lavender.git
synced 2024-12-21 23:24:09 +00:00
Return to logged out with fetchUserInfo fails, updates to test client
This commit is contained in:
parent
b47d4c8ad3
commit
e1825ce1e8
3
go.mod
3
go.mod
@ -14,6 +14,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/rs/cors v1.10.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/oauth2 v0.16.0
|
||||
)
|
||||
@ -35,7 +36,7 @@ require (
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/rtred v0.1.2 // indirect
|
||||
github.com/tidwall/tinyqueue v0.1.1 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -100,6 +100,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
|
||||
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
@ -177,8 +179,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
|
@ -12,13 +12,11 @@
|
||||
<div>Log in as: <span>{{.LoginName}}</span></div>
|
||||
<div>
|
||||
<form method="POST" action="/login">
|
||||
<input type="hidden" name="origin" value="{{.Origin}}"/>
|
||||
<button type="submit" name="not-you" value="1">Not You?</button>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<form method="POST" action="/login">
|
||||
<input type="hidden" name="origin" value="{{.Origin}}"/>
|
||||
<input type="hidden" name="loginname" value="{{.LoginName}}"/>
|
||||
<button type="submit">Continue</button>
|
||||
</form>
|
||||
|
@ -10,7 +10,6 @@
|
||||
</header>
|
||||
<main>
|
||||
<form method="POST" action="/login">
|
||||
<input type="hidden" name="origin" value="{{.Origin}}"/>
|
||||
<div>
|
||||
<label for="field_loginname">Login Name:</label>
|
||||
<input type="text" name="loginname" id="field_loginname" required/>
|
||||
|
@ -67,8 +67,9 @@ func (h *HttpServer) OptionalAuthentication(next UserHandler) httprouter.Handle
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if auth.IsGuest() && h.readLoginDataCookie(rw, req, &auth) {
|
||||
return
|
||||
if auth.IsGuest() {
|
||||
// if this fails internally it just sees the user as logged out
|
||||
h.readLoginDataCookie(rw, req, &auth)
|
||||
}
|
||||
next(rw, req, params, auth)
|
||||
}
|
||||
|
@ -32,14 +32,12 @@ func (h *HttpServer) loginGet(rw http.ResponseWriter, req *http.Request, _ httpr
|
||||
if err == nil && cookie.Valid() == nil {
|
||||
pages.RenderPageTemplate(rw, "login-memory", map[string]any{
|
||||
"ServiceName": h.conf.ServiceName,
|
||||
"Origin": req.URL.Query().Get("origin"),
|
||||
"LoginName": cookie.Value,
|
||||
})
|
||||
return
|
||||
}
|
||||
pages.RenderPageTemplate(rw, "login", map[string]any{
|
||||
"ServiceName": h.conf.ServiceName,
|
||||
"Origin": req.URL.Query().Get("origin"),
|
||||
})
|
||||
}
|
||||
|
||||
@ -60,9 +58,6 @@ func (h *HttpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ http
|
||||
})
|
||||
http.Redirect(rw, req, (&url.URL{
|
||||
Path: "/login",
|
||||
RawQuery: url.Values{
|
||||
"origin": []string{req.PostFormValue("origin")},
|
||||
}.Encode(),
|
||||
}).String(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
@ -111,8 +106,9 @@ func (h *HttpServer) loginCallback(rw http.ResponseWriter, req *http.Request, _
|
||||
return
|
||||
}
|
||||
|
||||
sessionData, done := h.fetchUserInfo(rw, err, flowState.sso, token)
|
||||
if !done {
|
||||
sessionData := h.fetchUserInfo(rw, err, flowState.sso, token)
|
||||
if sessionData.ID == "" {
|
||||
http.Error(rw, "Failed to fetch user info", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -166,63 +162,57 @@ func (h *HttpServer) setLoginDataCookie(rw http.ResponseWriter, userId string, t
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *HttpServer) readLoginDataCookie(rw http.ResponseWriter, req *http.Request, u *UserAuth) bool {
|
||||
func (h *HttpServer) readLoginDataCookie(rw http.ResponseWriter, req *http.Request, u *UserAuth) {
|
||||
loginCookie, err := req.Cookie("lavender-login-data")
|
||||
if err != nil {
|
||||
return false
|
||||
return
|
||||
}
|
||||
decryptedBytes, err := base64.RawStdEncoding.DecodeString(loginCookie.Value)
|
||||
if err != nil {
|
||||
return false
|
||||
return
|
||||
}
|
||||
decryptedData, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, h.signingKey.PrivateKey(), decryptedBytes, []byte("lavender-login-data"))
|
||||
if err != nil {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(decryptedData)
|
||||
userId, err := buf.ReadString(0)
|
||||
if err != nil {
|
||||
return false
|
||||
return
|
||||
}
|
||||
userId = strings.TrimSuffix(userId, "\x00")
|
||||
|
||||
var token *oauth2.Token
|
||||
err = json.NewDecoder(buf).Decode(&token)
|
||||
if err != nil {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
sso := h.manager.FindServiceFromLogin(userId)
|
||||
if sso == nil {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
sessionData, done := h.fetchUserInfo(rw, err, sso, token)
|
||||
if !done {
|
||||
return true
|
||||
}
|
||||
|
||||
u.Data = sessionData
|
||||
return false
|
||||
u.Data = h.fetchUserInfo(rw, err, sso, token)
|
||||
}
|
||||
|
||||
func (h *HttpServer) fetchUserInfo(rw http.ResponseWriter, err error, sso *issuer.WellKnownOIDC, token *oauth2.Token) (SessionData, bool) {
|
||||
func (h *HttpServer) fetchUserInfo(rw http.ResponseWriter, err error, sso *issuer.WellKnownOIDC, token *oauth2.Token) SessionData {
|
||||
res, err := sso.OAuth2Config.Client(context.Background(), token).Get(sso.UserInfoEndpoint)
|
||||
if err != nil || res.StatusCode != http.StatusOK {
|
||||
return SessionData{}, false
|
||||
return SessionData{}
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var userInfoJson UserInfoFields
|
||||
if err := json.NewDecoder(res.Body).Decode(&userInfoJson); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return SessionData{}, false
|
||||
return SessionData{}
|
||||
}
|
||||
subject, ok := userInfoJson.GetString("sub")
|
||||
if !ok {
|
||||
http.Error(rw, "Invalid subject", http.StatusInternalServerError)
|
||||
return SessionData{}, false
|
||||
return SessionData{}
|
||||
}
|
||||
subject += "@" + sso.Config.Namespace
|
||||
|
||||
@ -231,5 +221,5 @@ func (h *HttpServer) fetchUserInfo(rw http.ResponseWriter, err error, sso *issue
|
||||
ID: subject,
|
||||
DisplayName: displayName,
|
||||
UserInfo: userInfoJson,
|
||||
}, true
|
||||
}
|
||||
}
|
||||
|
@ -169,7 +169,15 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
r.GET("/userinfo", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||
userInfoRequest := func(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) {
|
||||
rw.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
rw.Header().Set("Access-Control-Allow-Headers", "Authorization,Content-Type")
|
||||
rw.Header().Set("Access-Control-Allow-Origin", strings.TrimSuffix(req.Referer(), "/"))
|
||||
rw.Header().Set("Access-Control-Allow-Methods", "GET")
|
||||
if req.Method == http.MethodOptions {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := oauthSrv.ValidationBearerToken(req)
|
||||
if err != nil {
|
||||
http.Error(rw, "403 Forbidden", http.StatusForbidden)
|
||||
@ -190,7 +198,9 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser
|
||||
m["updated_at"] = time.Now().Unix()
|
||||
|
||||
_ = json.NewEncoder(rw).Encode(m)
|
||||
})
|
||||
}
|
||||
r.GET("/userinfo", userInfoRequest)
|
||||
r.OPTIONS("/userinfo", userInfoRequest)
|
||||
|
||||
return &http.Server{
|
||||
Addr: conf.Listen,
|
||||
|
@ -1,92 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Test Client</title>
|
||||
<script src="popup.js"></script>
|
||||
<script>
|
||||
let currentTokens = null;
|
||||
const ssoService = "http://localhost:9090";
|
||||
<title>Test Client</title>
|
||||
<script src="pop2.js"></script>
|
||||
<script>
|
||||
const ssoService = "http://localhost:9090";
|
||||
|
||||
POP2.init(ssoService + "/authorize", "f4cdb93d-fe28-427b-b037-f03f44c86a16", "openid profile", 500, 600);
|
||||
POP2.init(ssoService + "/authorize", "f4cdb93d-fe28-427b-b037-f03f44c86a16", "openid profile", 500, 600);
|
||||
|
||||
function updateTokenInfo(data) {
|
||||
currentTokens = data.tokens;
|
||||
data.tokens = {
|
||||
access: "*****",
|
||||
refresh: "*****",
|
||||
}
|
||||
document.getElementById("someTextArea").textContent = JSON.stringify(data, null, 2);
|
||||
let perms = document.getElementById("somePerms");
|
||||
while (perms.childNodes.length > 0) {
|
||||
perms.childNodes.item(0).remove();
|
||||
}
|
||||
document.getElementById("tokenValues").textContent = JSON.stringify(currentTokens, null, 2);
|
||||
function updateTokenInfo(data) {
|
||||
document.getElementById("someTextArea").textContent = JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
let jwt = parseJwt(currentTokens.access);
|
||||
if (jwt.per != null) {
|
||||
jwt.per.forEach(function (x) {
|
||||
let a = document.createElement("li");
|
||||
a.textContent = x;
|
||||
perms.appendChild(a);
|
||||
});
|
||||
}
|
||||
}
|
||||
function parseJwt(token) {
|
||||
const base64Url = token.split('.')[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
return JSON.parse(jsonPayload);
|
||||
}
|
||||
|
||||
function parseJwt(token) {
|
||||
const base64Url = token.split('.')[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
return JSON.parse(jsonPayload);
|
||||
}
|
||||
function doThisThing() {
|
||||
POP2.clientRequest(ssoService + "/userinfo", {}, true).then(function (x) {
|
||||
console.log(x);
|
||||
}).catch(function (x) {
|
||||
console.error(x);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
function doThisThing() {
|
||||
POP2.getToken(function (token) {
|
||||
console.log(token);
|
||||
});
|
||||
}
|
||||
#someTextArea {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
#someTextArea {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#tokenValues {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
</style>
|
||||
#tokenValues {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Test Client</h1>
|
||||
<h1>Test Client</h1>
|
||||
</header>
|
||||
<main>
|
||||
<div>
|
||||
<button onclick="doThisThing();">Login</button>
|
||||
</div>
|
||||
<div style="display:flex; gap: 2em;">
|
||||
<div>
|
||||
<button onclick="doThisThing();">Login</button>
|
||||
<div>
|
||||
<label for="someTextArea"></label><textarea id="someTextArea"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="tokenValues"></label><textarea id="tokenValues"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap: 2em;">
|
||||
<div>
|
||||
<div>
|
||||
<label for="someTextArea"></label><textarea id="someTextArea"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="tokenValues"></label><textarea id="tokenValues"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>Permissions:</p>
|
||||
<ul id="somePerms"></ul>
|
||||
</div>
|
||||
<div>
|
||||
<p>Permissions:</p>
|
||||
<ul id="somePerms"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -71,7 +71,7 @@
|
||||
client_id,
|
||||
scope = '',
|
||||
redirect_uri = window.location.href.substr(0, window.location.href.length - window.location.hash.length).replace(/#$/, ''),
|
||||
access_token,
|
||||
access_token = localStorage.getItem("pop2_access_token"),
|
||||
callbackWaitForToken,
|
||||
w_width = 400,
|
||||
w_height = 360;
|
||||
@ -91,10 +91,12 @@
|
||||
receiveToken: function (token, expires_in) {
|
||||
if (token !== 'ERROR') {
|
||||
access_token = token;
|
||||
localStorage.setItem("pop2_access_token", access_token);
|
||||
if (callbackWaitForToken) callbackWaitForToken(access_token);
|
||||
setTimeout(
|
||||
function () {
|
||||
access_token = undefined;
|
||||
localStorage.removeItem("pop2_access_token");
|
||||
},
|
||||
expires_in * 1000
|
||||
);
|
||||
@ -129,6 +131,31 @@
|
||||
} else {
|
||||
return callback(access_token);
|
||||
}
|
||||
},
|
||||
clientRequest: function (resource, options, refresh = false) {
|
||||
const sendRequest = function () {
|
||||
options.credentials = 'include';
|
||||
if (options.headers) {
|
||||
options.headers['Authorization'] = 'Bearer ' + access_token;
|
||||
}
|
||||
return fetch(resource, options);
|
||||
};
|
||||
if (!refresh) return sendRequest();
|
||||
else {
|
||||
return new Promise(function (res, rej) {
|
||||
sendRequest().then(function (x) {
|
||||
res(x)
|
||||
}).catch(function () {
|
||||
w.POP2.getToken(function () {
|
||||
sendRequest().then(function (x) {
|
||||
res(x);
|
||||
}).catch(function (x) {
|
||||
rej(x);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
})(this);
|
||||
})(window);
|
Loading…
Reference in New Issue
Block a user