feature: Login/Logout links (#443)
* feature: Login/Logout links * cicd: codestyle
This commit is contained in:
parent
1af2e92132
commit
0283b51eca
|
|
@ -46,6 +46,7 @@ message GetDashboardComponentsResponse {
|
||||||
repeated DashboardComponent dashboards = 4;
|
repeated DashboardComponent dashboards = 4;
|
||||||
|
|
||||||
string authenticated_user = 5;
|
string authenticated_user = 5;
|
||||||
|
string authenticated_user_provider = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetDashboardComponentsRequest {}
|
message GetDashboardComponentsRequest {}
|
||||||
|
|
@ -225,6 +226,8 @@ message PasswordHashRequest {
|
||||||
message PasswordHashResponse {
|
message PasswordHashResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LogoutRequest {}
|
||||||
|
|
||||||
service OliveTinApiService {
|
service OliveTinApiService {
|
||||||
rpc GetDashboardComponents(GetDashboardComponentsRequest) returns (GetDashboardComponentsResponse) {
|
rpc GetDashboardComponents(GetDashboardComponentsRequest) returns (GetDashboardComponentsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
|
|
@ -328,4 +331,10 @@ service OliveTinApiService {
|
||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc Logout(LogoutRequest) returns (google.api.HttpBody) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/api/Logout"
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ type AuthenticatedUser struct {
|
||||||
Username string
|
Username string
|
||||||
Usergroup string
|
Usergroup string
|
||||||
|
|
||||||
|
Provider string
|
||||||
|
SID string
|
||||||
|
|
||||||
acls []string
|
acls []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,13 +83,15 @@ func permissionsConfigToBits(permissions config.PermissionsList) PermissionBits
|
||||||
func aclCheck(requiredPermission PermissionBits, defaultValue bool, cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action) bool {
|
func aclCheck(requiredPermission PermissionBits, defaultValue bool, cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action) bool {
|
||||||
relevantAcls := getRelevantAcls(cfg, action.Acls, user)
|
relevantAcls := getRelevantAcls(cfg, action.Acls, user)
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
if cfg.LogDebugOptions.AclCheckStarted {
|
||||||
"actionTitle": action.Title,
|
log.WithFields(log.Fields{
|
||||||
"username": user.Username,
|
"actionTitle": action.Title,
|
||||||
"usergroup": user.Usergroup,
|
"username": user.Username,
|
||||||
"relevantAcls": len(relevantAcls),
|
"usergroup": user.Usergroup,
|
||||||
"requiredPermission": requiredPermission,
|
"relevantAcls": len(relevantAcls),
|
||||||
}).Debugf("ACL check - %v", aclFunction)
|
"requiredPermission": requiredPermission,
|
||||||
|
}).Debugf("ACL check - %v", aclFunction)
|
||||||
|
}
|
||||||
|
|
||||||
for _, acl := range relevantAcls {
|
for _, acl := range relevantAcls {
|
||||||
permissionBits := permissionsConfigToBits(acl.Permissions)
|
permissionBits := permissionsConfigToBits(acl.Permissions)
|
||||||
|
|
@ -134,10 +139,6 @@ func getMetadataKeyOrEmpty(md metadata.MD, key string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetUserFromMetadata(md metadata.MD) string {
|
|
||||||
return getMetadataKeyOrEmpty(md, "set-user")
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserFromContext tries to find a user from a grpc context
|
// UserFromContext tries to find a user from a grpc context
|
||||||
func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser {
|
func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser {
|
||||||
var ret *AuthenticatedUser
|
var ret *AuthenticatedUser
|
||||||
|
|
@ -148,6 +149,7 @@ func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser
|
||||||
ret = &AuthenticatedUser{}
|
ret = &AuthenticatedUser{}
|
||||||
ret.Username = getMetadataKeyOrEmpty(md, "username")
|
ret.Username = getMetadataKeyOrEmpty(md, "username")
|
||||||
ret.Usergroup = getMetadataKeyOrEmpty(md, "usergroup")
|
ret.Usergroup = getMetadataKeyOrEmpty(md, "usergroup")
|
||||||
|
ret.Provider = getMetadataKeyOrEmpty(md, "provider")
|
||||||
|
|
||||||
buildUserAcls(cfg, ret)
|
buildUserAcls(cfg, ret)
|
||||||
}
|
}
|
||||||
|
|
@ -159,6 +161,7 @@ func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"username": ret.Username,
|
"username": ret.Username,
|
||||||
"usergroup": ret.Usergroup,
|
"usergroup": ret.Usergroup,
|
||||||
|
"provider": ret.Provider,
|
||||||
}).Debugf("UserFromContext")
|
}).Debugf("UserFromContext")
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -168,6 +171,7 @@ func UserGuest(cfg *config.Config) *AuthenticatedUser {
|
||||||
ret := &AuthenticatedUser{}
|
ret := &AuthenticatedUser{}
|
||||||
ret.Username = "guest"
|
ret.Username = "guest"
|
||||||
ret.Usergroup = "guest"
|
ret.Usergroup = "guest"
|
||||||
|
ret.Provider = "system"
|
||||||
|
|
||||||
buildUserAcls(cfg, ret)
|
buildUserAcls(cfg, ret)
|
||||||
|
|
||||||
|
|
@ -178,6 +182,7 @@ func UserFromSystem(cfg *config.Config, username string) *AuthenticatedUser {
|
||||||
ret := &AuthenticatedUser{
|
ret := &AuthenticatedUser{
|
||||||
Username: username,
|
Username: username,
|
||||||
Usergroup: "system",
|
Usergroup: "system",
|
||||||
|
Provider: "system",
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUserAcls(cfg, ret)
|
buildUserAcls(cfg, ret)
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,7 @@ type SaveLogsConfig struct {
|
||||||
type LogDebugOptions struct {
|
type LogDebugOptions struct {
|
||||||
SingleFrontendRequests bool
|
SingleFrontendRequests bool
|
||||||
SingleFrontendRequestHeaders bool
|
SingleFrontendRequestHeaders bool
|
||||||
|
AclCheckStarted bool
|
||||||
AclMatched bool
|
AclMatched bool
|
||||||
AclNotMatched bool
|
AclNotMatched bool
|
||||||
AclNoneMatched bool
|
AclNoneMatched bool
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,15 @@ func (api *oliveTinAPI) WatchExecution(req *pb.WatchExecutionRequest, srv pb.Oli
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
func (api *oliveTinAPI) Logout(ctx ctx.Context, req *pb.LogoutRequest) (*httpbody.HttpBody, error) {
|
||||||
|
user := acl.UserFromContext(ctx, cfg)
|
||||||
|
|
||||||
|
grpc.SendHeader(ctx, metadata.Pairs("logout-provider", user.Provider))
|
||||||
|
grpc.SendHeader(ctx, metadata.Pairs("logout-sid", user.SID))
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *pb.GetDashboardComponentsRequest) (*pb.GetDashboardComponentsResponse, error) {
|
func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *pb.GetDashboardComponentsRequest) (*pb.GetDashboardComponentsResponse, error) {
|
||||||
user := acl.UserFromContext(ctx, cfg)
|
user := acl.UserFromContext(ctx, cfg)
|
||||||
|
|
||||||
|
|
@ -298,6 +307,7 @@ func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *pb.GetDashb
|
||||||
dashboardCfgToPb(res, cfg.Dashboards, cfg)
|
dashboardCfgToPb(res, cfg.Dashboards, cfg)
|
||||||
|
|
||||||
res.AuthenticatedUser = user.Username
|
res.AuthenticatedUser = user.Username
|
||||||
|
res.AuthenticatedUserProvider = user.Provider
|
||||||
|
|
||||||
if res.AuthenticatedUser == "guest" && !cfg.AuthAllowGuest {
|
if res.AuthenticatedUser == "guest" && !cfg.AuthAllowGuest {
|
||||||
return nil, status.Errorf(codes.PermissionDenied, "Unauthenticated")
|
return nil, status.Errorf(codes.PermissionDenied, "Unauthenticated")
|
||||||
|
|
|
||||||
|
|
@ -50,26 +50,34 @@ func parseHttpHeaderForAuth(req *http.Request) (string, string) {
|
||||||
func parseRequestMetadata(ctx context.Context, req *http.Request) metadata.MD {
|
func parseRequestMetadata(ctx context.Context, req *http.Request) metadata.MD {
|
||||||
username := ""
|
username := ""
|
||||||
usergroup := ""
|
usergroup := ""
|
||||||
|
provider := "unknown"
|
||||||
|
sid := ""
|
||||||
|
|
||||||
if cfg.AuthJwtCookieName != "" {
|
if cfg.AuthJwtCookieName != "" {
|
||||||
username, usergroup = parseJwtCookie(req)
|
username, usergroup = parseJwtCookie(req)
|
||||||
|
provider = "jwt-cookie"
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.AuthHttpHeaderUsername != "" {
|
if cfg.AuthHttpHeaderUsername != "" {
|
||||||
username, usergroup = parseHttpHeaderForAuth(req)
|
username, usergroup = parseHttpHeaderForAuth(req)
|
||||||
|
provider = "http-header"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.AuthOAuth2Providers) > 0 {
|
if len(cfg.AuthOAuth2Providers) > 0 {
|
||||||
username, usergroup = parseOAuth2Cookie(req)
|
username, usergroup, sid = parseOAuth2Cookie(req)
|
||||||
|
provider = "oauth2"
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.AuthLocalUsers.Enabled {
|
if cfg.AuthLocalUsers.Enabled && username == "" {
|
||||||
username, usergroup = parseLocalUserCookie(req)
|
username, usergroup, sid = parseLocalUserCookie(req)
|
||||||
|
provider = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
md := metadata.New(map[string]string{
|
md := metadata.New(map[string]string{
|
||||||
"username": username,
|
"username": username,
|
||||||
"usergroup": usergroup,
|
"usergroup": usergroup,
|
||||||
|
"provider": provider,
|
||||||
|
"sid": sid,
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Tracef("api request metadata: %+v", md)
|
log.Tracef("api request metadata: %+v", md)
|
||||||
|
|
@ -78,11 +86,57 @@ func parseRequestMetadata(ctx context.Context, req *http.Request) metadata.MD {
|
||||||
}
|
}
|
||||||
|
|
||||||
func forwardResponseHandler(ctx context.Context, w http.ResponseWriter, msg protoreflect.ProtoMessage) error {
|
func forwardResponseHandler(ctx context.Context, w http.ResponseWriter, msg protoreflect.ProtoMessage) error {
|
||||||
forwardResponseHandlerLoginLocalUser(ctx, w, msg)
|
md, ok := runtime.ServerMetadataFromContext(ctx)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
log.Warn("Could not get ServerMetadata from context")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardResponseHandlerLoginLocalUser(md.HeaderMD, w)
|
||||||
|
forwardResponseHandlerLogout(md.HeaderMD, w)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func forwardResponseHandlerLogout(md metadata.MD, w http.ResponseWriter) {
|
||||||
|
if getMetadataKeyOrEmpty(md, "logout-provider") != "" {
|
||||||
|
sid := getMetadataKeyOrEmpty(md, "logout-sid")
|
||||||
|
|
||||||
|
delete(registeredStates, sid)
|
||||||
|
http.SetCookie(
|
||||||
|
w,
|
||||||
|
&http.Cookie{
|
||||||
|
Name: "olivetin-sid-oauth",
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
delete(localUserSessions, sid)
|
||||||
|
http.SetCookie(
|
||||||
|
w,
|
||||||
|
&http.Cookie{
|
||||||
|
Name: "olivetin-sid-local",
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
// We cannot send a HTTP redirect here, because we don't have access to req.
|
||||||
|
w.Write([]byte("<script>window.location.href = '/';</script>"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetadataKeyOrEmpty(md metadata.MD, key string) string {
|
||||||
|
mdValues := md.Get(key)
|
||||||
|
|
||||||
|
if len(mdValues) > 0 {
|
||||||
|
return mdValues[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func SetGlobalRestConfig(config *config.Config) {
|
func SetGlobalRestConfig(config *config.Config) {
|
||||||
cfg = config
|
cfg = config
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,22 @@
|
||||||
package httpservers
|
package httpservers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"google.golang.org/grpc/metadata"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
acl "github.com/OliveTin/OliveTin/internal/acl"
|
|
||||||
"google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
localUserSessions = make(map[string]string) // sid -> username, used for local user sessions
|
localUserSessions = make(map[string]string) // sid -> username, used for local user sessions
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseLocalUserCookie(req *http.Request) (string, string) {
|
func parseLocalUserCookie(req *http.Request) (string, string, string) {
|
||||||
cookie, err := req.Cookie("olivetin_local_user_sid")
|
cookie, err := req.Cookie("olivetin-sid-local")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", ""
|
return "", "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
cookieValue := cookie.Value
|
cookieValue := cookie.Value
|
||||||
|
|
@ -28,30 +25,23 @@ func parseLocalUserCookie(req *http.Request) (string, string) {
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warnf("Could not find local user session: %v", cookieValue)
|
log.Warnf("Could not find local user session: %v", cookieValue)
|
||||||
return "", ""
|
return "", "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return username, ""
|
return username, "", cookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func forwardResponseHandlerLoginLocalUser(ctx context.Context, w http.ResponseWriter, msg protoreflect.ProtoMessage) error {
|
func forwardResponseHandlerLoginLocalUser(md metadata.MD, w http.ResponseWriter) error {
|
||||||
md, ok := runtime.ServerMetadataFromContext(ctx)
|
setUser := getMetadataKeyOrEmpty(md, "set-user")
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Warn("Could not get ServerMetadata from context")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
setUser := acl.SetUserFromMetadata(md.HeaderMD)
|
|
||||||
|
|
||||||
sid := uuid.NewString()
|
|
||||||
localUserSessions[sid] = setUser
|
|
||||||
|
|
||||||
if setUser != "" {
|
if setUser != "" {
|
||||||
|
sid := uuid.NewString()
|
||||||
|
localUserSessions[sid] = setUser
|
||||||
|
|
||||||
http.SetCookie(
|
http.SetCookie(
|
||||||
w,
|
w,
|
||||||
&http.Cookie{
|
&http.Cookie{
|
||||||
Name: "olivetin_local_user_sid",
|
Name: "olivetin-sid-local",
|
||||||
Value: sid,
|
Value: sid,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ func handleOAuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
registeredStates[state] = &oauth2State{
|
registeredStates[state] = &oauth2State{
|
||||||
provider: provider,
|
provider: provider,
|
||||||
|
Username: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -126,45 +127,46 @@ func handleOAuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setOauthCallbackCookie(w, r, "oauth2state", state)
|
setOauthCallbackCookie(w, r, "olivetin-sid-oauth", state)
|
||||||
|
|
||||||
log.Infof("OAuth2 state: %v mapped to provider %v (found: %v), now redirecting", state, providerName, provider != nil)
|
log.Infof("OAuth2 state: %v mapped to provider %v (found: %v), now redirecting", state, providerName, provider != nil)
|
||||||
|
|
||||||
http.Redirect(w, r, provider.AuthCodeURL(state), http.StatusFound)
|
http.Redirect(w, r, provider.AuthCodeURL(state), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkOAuthCallbackCookie(w http.ResponseWriter, r *http.Request) (*oauth2State, bool) {
|
func checkOAuthCallbackCookie(w http.ResponseWriter, r *http.Request) (*oauth2State, string, bool) {
|
||||||
state, err := r.Cookie("oauth2state")
|
cookie, err := r.Cookie("olivetin-sid-oauth")
|
||||||
|
state := cookie.Value
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to get state cookie: %v", err)
|
log.Errorf("Failed to get state cookie: %v", err)
|
||||||
|
|
||||||
http.Error(w, "State not found", http.StatusBadRequest)
|
http.Error(w, "State not found", http.StatusBadRequest)
|
||||||
return nil, false
|
return nil, state, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Query().Get("state") != state.Value {
|
if r.URL.Query().Get("state") != state {
|
||||||
log.Errorf("State mismatch: %v != %v", r.URL.Query().Get("state"), state.Value)
|
log.Errorf("State mismatch: %v != %v", r.URL.Query().Get("state"), state)
|
||||||
|
|
||||||
http.Error(w, "State mismatch", http.StatusBadRequest)
|
http.Error(w, "State mismatch", http.StatusBadRequest)
|
||||||
return nil, false
|
return nil, state, false
|
||||||
}
|
}
|
||||||
|
|
||||||
registeredState, ok := registeredStates[state.Value]
|
registeredState, ok := registeredStates[state]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("State not found in server: %v", state.Value)
|
log.Errorf("State not found in server: %v", state)
|
||||||
|
|
||||||
http.Error(w, "State not found in server", http.StatusBadRequest)
|
http.Error(w, "State not found in server", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return registeredState, true
|
return registeredState, state, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleOAuthCallback(w http.ResponseWriter, r *http.Request) {
|
func handleOAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Infof("OAuth2 Callback received")
|
log.Infof("OAuth2 Callback received")
|
||||||
|
|
||||||
registeredState, ok := checkOAuthCallbackCookie(w, r)
|
registeredState, state, ok := checkOAuthCallbackCookie(w, r)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
|
@ -172,7 +174,10 @@ func handleOAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
code := r.FormValue("code")
|
code := r.FormValue("code")
|
||||||
|
|
||||||
log.Debugf("OAuth2 Token Code: %v", code)
|
log.WithFields(log.Fields{
|
||||||
|
"state": state,
|
||||||
|
"token-code": code,
|
||||||
|
}).Debug("OAuth2 Token Code")
|
||||||
|
|
||||||
httpClient := &http.Client{Timeout: 2 * time.Second}
|
httpClient := &http.Client{Timeout: 2 * time.Second}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
@ -188,12 +193,21 @@ func handleOAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
client := registeredState.provider.Client(ctx, tok)
|
client := registeredState.provider.Client(ctx, tok)
|
||||||
|
|
||||||
registeredState.Username = getUsername(client)
|
username := getUsername(client)
|
||||||
|
|
||||||
loginMessage := fmt.Sprintf("Logged in as %v", registeredState.Username)
|
registeredStates[state].Username = username
|
||||||
|
|
||||||
log.Infof(loginMessage)
|
for k, v := range registeredStates {
|
||||||
|
log.Debugf("states: %+v %+v", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginMessage := fmt.Sprintf("OAuth2 login complete for %v", registeredStates[state].Username)
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"state": state,
|
||||||
|
}).Infof(loginMessage)
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
w.Write([]byte(loginMessage))
|
w.Write([]byte(loginMessage))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,20 +246,22 @@ func getUsername(client *http.Client) string {
|
||||||
return username.(string)
|
return username.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOAuth2Cookie(r *http.Request) (string, string) {
|
func parseOAuth2Cookie(r *http.Request) (string, string, string) {
|
||||||
cookie, err := r.Cookie("oauth2state")
|
cookie, err := r.Cookie("olivetin-sid-oauth")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to read OAuth2 cookie: %v", err)
|
log.Warnf("Failed to read OAuth2 cookie: %v", err)
|
||||||
return "", ""
|
return "", "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
serverState, found := registeredStates[cookie.Value]
|
serverState, found := registeredStates[cookie.Value]
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
log.Warnf("Failed to find OAuth2 state: %v", cookie.Value)
|
log.Warnf("Failed to find OAuth2 state: %v", cookie.Value)
|
||||||
return "", ""
|
return "", "", cookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
return serverState.Username, serverState.Usergroup
|
log.Debugf("Found OAuth2 state: %+v", serverState)
|
||||||
|
|
||||||
|
return serverState.Username, serverState.Usergroup, cookie.Value
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +44,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<span id = "username"> </span>
|
<div class = "userinfo">
|
||||||
|
<span id = "link-login" hidden><a href = "/login">Login</a> |</span>
|
||||||
|
<span id = "link-logout" hidden><a href = "/api/Logout">Logout</a> |</span>
|
||||||
|
<span id = "username"> </span>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1.5em" height="1.5em" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="m12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036q-.016-.004-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10s10-4.477 10-10S17.523 2 12 2M8.5 9.5a3.5 3.5 0 1 1 7 0a3.5 3.5 0 0 1-7 0m9.758 7.484A7.99 7.99 0 0 1 12 20a7.99 7.99 0 0 1-6.258-3.016C7.363 15.821 9.575 15 12 15s4.637.821 6.258 1.984"/></g></svg>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main title = "main content">
|
<main title = "main content">
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export class LoginForm extends window.HTMLElement {
|
||||||
}
|
}
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
window.location.reload()
|
window.location.href = '/'
|
||||||
} else {
|
} else {
|
||||||
document.querySelector('.error').innerHTML = 'Login failed.'
|
document.querySelector('.error').innerHTML = 'Login failed.'
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +59,14 @@ export class LoginForm extends window.HTMLElement {
|
||||||
for (const provider of providers) {
|
for (const provider of providers) {
|
||||||
const providerForm = document.createElement('form')
|
const providerForm = document.createElement('form')
|
||||||
providerForm.method = 'GET'
|
providerForm.method = 'GET'
|
||||||
providerForm.action = '/oauth2?provider=' + provider.Name
|
providerForm.action = '/oauth/login'
|
||||||
|
|
||||||
|
const hiddenField = document.createElement('input')
|
||||||
|
hiddenField.type = 'hidden'
|
||||||
|
hiddenField.name = 'provider'
|
||||||
|
hiddenField.value = provider.Name
|
||||||
|
|
||||||
|
providerForm.appendChild(hiddenField)
|
||||||
|
|
||||||
const providerButton = document.createElement('button')
|
const providerButton = document.createElement('button')
|
||||||
providerButton.type = 'submit'
|
providerButton.type = 'submit'
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,21 @@ export function marshalDashboardComponentsJsonToHtml (json) {
|
||||||
|
|
||||||
document.getElementById('username').innerText = json.authenticatedUser
|
document.getElementById('username').innerText = json.authenticatedUser
|
||||||
|
|
||||||
|
if (window.settings.AuthLocalLogin || window.settings.AuthLocalRegister != null) {
|
||||||
|
if (json.authenticatedUser === 'guest') {
|
||||||
|
document.getElementById('link-login').hidden = false
|
||||||
|
document.getElementById('link-logout').hidden = true
|
||||||
|
} else {
|
||||||
|
document.getElementById('link-login').hidden = true
|
||||||
|
|
||||||
|
if (json.authenticatedUserProvider === 'local' || json.authenticatedUserProvider === 'oauth2') {
|
||||||
|
document.getElementById('link-logout').hidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('username').setAttribute('title', json.authenticatedUserProvider)
|
||||||
|
}
|
||||||
|
|
||||||
document.body.setAttribute('initial-marshal-complete', 'true')
|
document.body.setAttribute('initial-marshal-complete', 'true')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,11 +98,15 @@ nav ul li a.selected {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#username {
|
.userinfo {
|
||||||
margin-right: 1em;
|
padding-right: 1em;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.userinfo svg, .userinfo span {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
left: -250px;
|
left: -250px;
|
||||||
}
|
}
|
||||||
|
|
@ -150,6 +154,7 @@ h1 {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
padding-left: .5em;
|
padding-left: .5em;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog h1 {
|
dialog h1 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue