diff --git a/OliveTin.proto b/OliveTin.proto index 56484c9..3274605 100644 --- a/OliveTin.proto +++ b/OliveTin.proto @@ -215,7 +215,14 @@ message LocalUserLoginRequest { } message LocalUserLoginResponse { - string authenticated_user = 1; + bool success = 1; +} + +message PasswordHashRequest { + string password = 1; +} + +message PasswordHashResponse { } service OliveTinApiService { @@ -308,9 +315,16 @@ service OliveTinApiService { }; } - rpc Authenticate(LocalUserLoginRequest) returns (LocalUserLoginResponse) { + rpc LocalUserLogin(LocalUserLoginRequest) returns (LocalUserLoginResponse) { option (google.api.http) = { - post: "/api/authenticate" + post: "/api/LocalUserLogin" + body: "*" + }; + } + + rpc PasswordHash(PasswordHashRequest) returns (google.api.HttpBody) { + option (google.api.http) = { + post: "/api/PasswordHash" body: "*" }; } diff --git a/go.mod b/go.mod index a0362aa..45f63d7 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/MicahParks/jwkset v0.5.17 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/alexedwards/argon2id v1.0.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bufbuild/protocompile v0.9.0 // indirect diff --git a/go.sum b/go.sum index 7720fc7..1444e69 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/MicahParks/keyfunc/v3 v3.3.2 h1:YTtwc4dxalBZKFqHhqctBWN6VhbLdGhywmne9 github.com/MicahParks/keyfunc/v3 v3.3.2/go.mod h1:GJBeEjnv25OnD9y2OYQa7ELU6gYahEMBNXINZb+qm34= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w= +github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -371,6 +373,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -409,7 +412,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -451,6 +456,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -484,6 +491,9 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -507,6 +517,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -546,13 +558,22 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -561,6 +582,10 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -617,6 +642,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/acl/acl.go b/internal/acl/acl.go index 3562071..c720a2a 100644 --- a/internal/acl/acl.go +++ b/internal/acl/acl.go @@ -134,6 +134,10 @@ func getMetadataKeyOrEmpty(md metadata.MD, key string) string { return "" } +func SetUserFromMetadata(md metadata.MD) string { + return getMetadataKeyOrEmpty(md, "set-user") +} + // UserFromContext tries to find a user from a grpc context func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser { var ret *AuthenticatedUser diff --git a/internal/config/config.go b/internal/config/config.go index 57f393e..c68c35d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -115,7 +115,7 @@ type Config struct { AuthJwtPubKeyPath string // will read pub key from file on disk AuthHttpHeaderUsername string AuthHttpHeaderUserGroup string - AuthUsernamePassword AuthUsernamePasswordConfig + AuthLocalUsers AuthLocalUsersConfig AuthLoginUrl string AuthAllowGuest bool AuthOAuth2RedirectURL string @@ -140,7 +140,7 @@ type Config struct { usedConfigDir string } -type AuthUsernamePasswordConfig struct { +type AuthLocalUsersConfig struct { Enabled bool Users []*LocalUser } diff --git a/internal/grpcapi/grpcApi.go b/internal/grpcapi/grpcApi.go index ec73c8a..6580c16 100644 --- a/internal/grpcapi/grpcApi.go +++ b/internal/grpcapi/grpcApi.go @@ -9,6 +9,7 @@ import ( "google.golang.org/genproto/googleapis/api/httpbody" "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "errors" @@ -94,16 +95,31 @@ func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest) }, nil } -func (api *oliveTinAPI) Authenticate(ctx ctx.Context, req *pb.LocalUserLoginRequest) (*pb.LocalUserLoginResponse, error) { -/* - user := acl.Authenticate(cfg, req.Username, req.Password) +func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *pb.PasswordHashRequest) (*httpbody.HttpBody, error) { + hash, err := createHash(req.Password) - if user == nil { - return nil, status.Errorf(codes.Unauthenticated, "Invalid username or password.") + if err != nil { + return nil, status.Errorf(codes.Internal, "Error creating hash.") + } + + ret := &httpbody.HttpBody{ + ContentType: "text/plain", + Data: []byte("Your password hash is: " + hash), + } + + return ret, nil +} + +func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *pb.LocalUserLoginRequest) (*pb.LocalUserLoginResponse, error) { + match := checkUserPassword(cfg, req.Username, req.Password) + + if match { + header := metadata.Pairs("set-user", req.Username) + grpc.SendHeader(ctx, header) } -*/ return &pb.LocalUserLoginResponse{ + Success: match, }, nil } diff --git a/internal/grpcapi/local_user_login.go b/internal/grpcapi/local_user_login.go new file mode 100644 index 0000000..139aa92 --- /dev/null +++ b/internal/grpcapi/local_user_login.go @@ -0,0 +1,52 @@ +package grpcapi + +import ( + config "github.com/OliveTin/OliveTin/internal/config" + "github.com/alexedwards/argon2id" + log "github.com/sirupsen/logrus" + "runtime" +) + +var defaultParams = argon2id.Params{ + Memory: 64 * 1024, + Iterations: 4, + Parallelism: uint8(runtime.NumCPU()), + SaltLength: 16, + KeyLength: 32, +} + +func createHash(password string) (string, error) { + hash, err := argon2id.CreateHash(password, &defaultParams) + + if err != nil { + log.Fatal("Error creating hash: ", err) + return "", err + } + + return hash, nil +} + +func comparePasswordAndHash(password, hash string) bool { + match, err := argon2id.ComparePasswordAndHash(password, hash) + + if err != nil { + log.Errorf("Error comparing password and hash: %v", err) + return false + } + + return match +} + +func checkUserPassword(cfg *config.Config, username, password string) bool { + for _, user := range cfg.AuthLocalUsers.Users { + if user.Username == username { + match := comparePasswordAndHash(password, user.Password) + + if match { + return true + } + } + } + + return false +} diff --git a/internal/httpservers/restapi.go b/internal/httpservers/restapi.go index fa2afb0..9052b7c 100644 --- a/internal/httpservers/restapi.go +++ b/internal/httpservers/restapi.go @@ -7,6 +7,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/reflect/protoreflect" "net/http" gw "github.com/OliveTin/OliveTin/gen/grpc" @@ -45,6 +46,7 @@ func parseHttpHeaderForAuth(req *http.Request) (string, string) { return username[0], "" } +//gocyclo:ignore func parseRequestMetadata(ctx context.Context, req *http.Request) metadata.MD { username := "" usergroup := "" @@ -61,6 +63,10 @@ func parseRequestMetadata(ctx context.Context, req *http.Request) metadata.MD { username, usergroup = parseOAuth2Cookie(req) } + if cfg.AuthLocalUsers.Enabled { + username, usergroup = parseLocalUserCookie(req) + } + md := metadata.New(map[string]string{ "username": username, "usergroup": usergroup, @@ -71,6 +77,12 @@ func parseRequestMetadata(ctx context.Context, req *http.Request) metadata.MD { return md } +func forwardResponseHandler(ctx context.Context, w http.ResponseWriter, msg protoreflect.ProtoMessage) error { + forwardResponseHandlerLoginLocalUser(ctx, w, msg) + + return nil +} + func SetGlobalRestConfig(config *config.Config) { cfg = config } @@ -91,6 +103,7 @@ func newMux() *runtime.ServeMux { // The MarshalOptions set some important compatibility settings for the webui. See below. mux := runtime.NewServeMux( runtime.WithMetadata(parseRequestMetadata), + runtime.WithForwardResponseOption(forwardResponseHandler), runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{ Marshaler: &runtime.JSONPb{ MarshalOptions: protojson.MarshalOptions{ diff --git a/internal/httpservers/restapi_auth_local.go b/internal/httpservers/restapi_auth_local.go new file mode 100644 index 0000000..36fe891 --- /dev/null +++ b/internal/httpservers/restapi_auth_local.go @@ -0,0 +1,61 @@ +package httpservers + +import ( + "context" + "net/http" + + "github.com/google/uuid" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + log "github.com/sirupsen/logrus" + + acl "github.com/OliveTin/OliveTin/internal/acl" + "google.golang.org/protobuf/reflect/protoreflect" +) + +var ( + localUserSessions = make(map[string]string) // sid -> username, used for local user sessions +) + +func parseLocalUserCookie(req *http.Request) (string, string) { + cookie, err := req.Cookie("olivetin_local_user_sid") + if err != nil { + return "", "" + } + + cookieValue := cookie.Value + + username, ok := localUserSessions[cookieValue] + + if !ok { + log.Warnf("Could not find local user session: %v", cookieValue) + return "", "" + } + + return username, "" +} + +func forwardResponseHandlerLoginLocalUser(ctx context.Context, w http.ResponseWriter, msg protoreflect.ProtoMessage) error { + md, ok := runtime.ServerMetadataFromContext(ctx) + + 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 != "" { + http.SetCookie( + w, + &http.Cookie{ + Name: "olivetin_local_user_sid", + Value: sid, + }, + ) + } + + return nil +} diff --git a/webui.dev/index.html b/webui.dev/index.html index ba158b9..fc04143 100644 --- a/webui.dev/index.html +++ b/webui.dev/index.html @@ -92,35 +92,6 @@ - - - - OAuth Login - - GitHub - Google - - - Local Login - - - Username: - - - - - - Password: - - - - - Login - - - - - Sorry, JavaScript is required to use OliveTin. @@ -181,7 +152,7 @@ - + Login with OAuth or Local users is not enabled on this server. @@ -192,17 +163,18 @@ Local Login + Username: - + Password: - + Login diff --git a/webui.dev/js/LoginForm.js b/webui.dev/js/LoginForm.js index 56c0e43..81d2abe 100644 --- a/webui.dev/js/LoginForm.js +++ b/webui.dev/js/LoginForm.js @@ -4,6 +4,47 @@ export class LoginForm extends window.HTMLElement { this.content = tpl.content.cloneNode(true) this.appendChild(this.content) + + this.querySelector('#local-user-login').addEventListener('submit', (e) => { + e.preventDefault() + this.localLoginRequest() + }) + } + + localLoginRequest () { + const username = this.querySelector('input.username').value + const password = this.querySelector('input.password').value + + document.querySelector('.error').innerHTML = '' + + window.fetch('/api/LocalUserLogin', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + username: username, + password: password + }) + }).then((response) => { + if (response.ok) { + const res = response.json() + + if (res !== undefined) { + return res + } else { + throw new Error('Failed to login - no res') + } + } else { + throw new Error('Failed to login') + } + }).then((res) => { + if (res.success) { + window.location.reload() + } else { + document.querySelector('.error').innerHTML = 'Login failed.' + } + }) } processOAuth2Providers (providers) {
Login with OAuth or Local users is not enabled on this server.