mirror of
https://github.com/1f349/lavender.git
synced 2024-12-22 07:34:06 +00:00
Add public to oauth client store and separate fetching user info
This commit is contained in:
parent
9228f6649e
commit
b99fb9df6f
@ -16,7 +16,7 @@ type User struct {
|
|||||||
|
|
||||||
type ClientInfoDbOutput struct {
|
type ClientInfoDbOutput struct {
|
||||||
Sub, Name, Secret, Domain, Owner string
|
Sub, Name, Secret, Domain, Owner string
|
||||||
SSO, Active bool
|
Public, SSO, Active bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ oauth2.ClientInfo = &ClientInfoDbOutput{}
|
var _ oauth2.ClientInfo = &ClientInfoDbOutput{}
|
||||||
@ -24,7 +24,7 @@ var _ oauth2.ClientInfo = &ClientInfoDbOutput{}
|
|||||||
func (c *ClientInfoDbOutput) GetID() string { return c.Sub }
|
func (c *ClientInfoDbOutput) GetID() string { return c.Sub }
|
||||||
func (c *ClientInfoDbOutput) GetSecret() string { return c.Secret }
|
func (c *ClientInfoDbOutput) GetSecret() string { return c.Secret }
|
||||||
func (c *ClientInfoDbOutput) GetDomain() string { return c.Domain }
|
func (c *ClientInfoDbOutput) GetDomain() string { return c.Domain }
|
||||||
func (c *ClientInfoDbOutput) IsPublic() bool { return false }
|
func (c *ClientInfoDbOutput) IsPublic() bool { return c.Public }
|
||||||
func (c *ClientInfoDbOutput) GetUserID() string { return c.Owner }
|
func (c *ClientInfoDbOutput) GetUserID() string { return c.Owner }
|
||||||
|
|
||||||
// GetName is an extra field for the oauth handler to display the application
|
// GetName is an extra field for the oauth handler to display the application
|
||||||
|
@ -15,6 +15,7 @@ CREATE TABLE IF NOT EXISTS client_store
|
|||||||
secret TEXT UNIQUE NOT NULL,
|
secret TEXT UNIQUE NOT NULL,
|
||||||
domain TEXT NOT NULL,
|
domain TEXT NOT NULL,
|
||||||
owner TEXT NOT NULL,
|
owner TEXT NOT NULL,
|
||||||
|
public INTEGER,
|
||||||
sso INTEGER,
|
sso INTEGER,
|
||||||
active INTEGER DEFAULT 1,
|
active INTEGER DEFAULT 1,
|
||||||
FOREIGN KEY (owner) REFERENCES users (subject)
|
FOREIGN KEY (owner) REFERENCES users (subject)
|
||||||
|
@ -65,8 +65,8 @@ func (t *Tx) GetUserEmail(sub string) (string, error) {
|
|||||||
|
|
||||||
func (t *Tx) GetClientInfo(sub string) (oauth2.ClientInfo, error) {
|
func (t *Tx) GetClientInfo(sub string) (oauth2.ClientInfo, error) {
|
||||||
var u ClientInfoDbOutput
|
var u ClientInfoDbOutput
|
||||||
row := t.tx.QueryRow(`SELECT secret, name, domain, sso, active FROM client_store WHERE subject = ? LIMIT 1`, sub)
|
row := t.tx.QueryRow(`SELECT secret, name, domain, public, sso, active FROM client_store WHERE subject = ? LIMIT 1`, sub)
|
||||||
err := row.Scan(&u.Secret, &u.Name, &u.Domain, &u.SSO, &u.Active)
|
err := row.Scan(&u.Secret, &u.Name, &u.Domain, &u.Public, &u.SSO, &u.Active)
|
||||||
u.Owner = sub
|
u.Owner = sub
|
||||||
if !u.Active {
|
if !u.Active {
|
||||||
return nil, fmt.Errorf("client is not active")
|
return nil, fmt.Errorf("client is not active")
|
||||||
@ -74,16 +74,16 @@ func (t *Tx) GetClientInfo(sub string) (oauth2.ClientInfo, error) {
|
|||||||
return &u, err
|
return &u, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tx) GetAppList(offset int) ([]ClientInfoDbOutput, error) {
|
func (t *Tx) GetAppList(owner string, admin bool, offset int) ([]ClientInfoDbOutput, error) {
|
||||||
var u []ClientInfoDbOutput
|
var u []ClientInfoDbOutput
|
||||||
row, err := t.tx.Query(`SELECT subject, name, domain, owner, sso, active FROM client_store LIMIT 25 OFFSET ?`, offset)
|
row, err := t.tx.Query(`SELECT subject, name, domain, owner, public, sso, active FROM client_store WHERE owner = ? OR ? = 1 LIMIT 25 OFFSET ?`, owner, admin, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer row.Close()
|
defer row.Close()
|
||||||
for row.Next() {
|
for row.Next() {
|
||||||
var a ClientInfoDbOutput
|
var a ClientInfoDbOutput
|
||||||
err := row.Scan(&a.Sub, &a.Name, &a.Domain, &a.Owner, &a.SSO, &a.Active)
|
err := row.Scan(&a.Sub, &a.Name, &a.Domain, &a.Owner, &a.Public, &a.SSO, &a.Active)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -92,18 +92,18 @@ func (t *Tx) GetAppList(offset int) ([]ClientInfoDbOutput, error) {
|
|||||||
return u, row.Err()
|
return u, row.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tx) InsertClientApp(name, domain string, sso, active bool, owner string) error {
|
func (t *Tx) InsertClientApp(name, domain string, public, sso, active bool, owner string) error {
|
||||||
u := uuid.New()
|
u := uuid.New()
|
||||||
secret, err := password.GenerateApiSecret(70)
|
secret, err := password.GenerateApiSecret(70)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = t.tx.Exec(`INSERT INTO client_store (subject, name, secret, domain, owner, sso, active) VALUES (?, ?, ?, ?, ?, ?, ?)`, u.String(), name, secret, domain, owner, sso, active)
|
_, err = t.tx.Exec(`INSERT INTO client_store (subject, name, secret, domain, owner, public, sso, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, u.String(), name, secret, domain, owner, public, sso, active)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tx) UpdateClientApp(subject uuid.UUID, owner string, name, domain string, sso, active bool) error {
|
func (t *Tx) UpdateClientApp(subject uuid.UUID, owner string, name, domain string, public, sso, active bool) error {
|
||||||
_, err := t.tx.Exec(`UPDATE client_store SET name = ?, domain = ?, sso = ?, active = ? WHERE subject = ? AND owner = ?`, name, domain, sso, active, subject.String(), owner)
|
_, err := t.tx.Exec(`UPDATE client_store SET name = ?, domain = ?, public = ?, sso = ?, active = ? WHERE subject = ? AND owner = ?`, name, domain, public, sso, active, subject.String(), owner)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
go.mod
18
go.mod
@ -13,7 +13,7 @@ require (
|
|||||||
github.com/google/subcommands v1.2.0
|
github.com/google/subcommands v1.2.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.18
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
golang.org/x/oauth2 v0.16.0
|
golang.org/x/oauth2 v0.16.0
|
||||||
)
|
)
|
||||||
@ -21,20 +21,20 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/MrMelon54/rescheduler v0.0.2 // indirect
|
github.com/MrMelon54/rescheduler v0.0.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 // indirect
|
github.com/tidwall/btree v1.7.0 // indirect
|
||||||
github.com/tidwall/buntdb v1.1.2 // indirect
|
github.com/tidwall/buntdb v1.3.0 // indirect
|
||||||
github.com/tidwall/gjson v1.12.1 // indirect
|
github.com/tidwall/gjson v1.17.0 // indirect
|
||||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb // indirect
|
github.com/tidwall/grect v0.1.4 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e // indirect
|
github.com/tidwall/rtred v0.1.2 // indirect
|
||||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect
|
github.com/tidwall/tinyqueue v0.1.1 // indirect
|
||||||
golang.org/x/net v0.20.0 // indirect
|
golang.org/x/net v0.20.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
|
32
go.sum
32
go.sum
@ -30,8 +30,9 @@ github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQ
|
|||||||
github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ=
|
github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ=
|
||||||
github.com/go-session/session v3.1.2+incompatible h1:yStchEObKg4nk2F7JGE7KoFIrA/1Y078peagMWcrncg=
|
github.com/go-session/session v3.1.2+incompatible h1:yStchEObKg4nk2F7JGE7KoFIrA/1Y078peagMWcrncg=
|
||||||
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
||||||
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
|
|
||||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@ -83,8 +84,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
@ -112,25 +113,36 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
|
||||||
|
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
|
||||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||||
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
|
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
|
||||||
|
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
||||||
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
|
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
|
||||||
|
github.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA=
|
||||||
|
github.com/tidwall/buntdb v1.3.0/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
|
||||||
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||||
github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
|
|
||||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE=
|
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||||
|
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
|
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
|
||||||
|
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
|
||||||
|
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
|
||||||
|
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
|
||||||
|
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
|
||||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2KoJQD9cTQ6dyP2co9q4yzmT9FZo=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
|
||||||
|
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
|
||||||
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
|
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
|
||||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE=
|
|
||||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
|
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
|
||||||
|
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
|
||||||
|
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
|
github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
|
||||||
|
@ -15,11 +15,13 @@
|
|||||||
<button type="submit">Manage Applications</button>
|
<button type="submit">Manage Applications</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
{{if .IsAdmin}}
|
||||||
<div>
|
<div>
|
||||||
<form method="GET" action="/manage/users">
|
<form method="GET" action="/manage/users">
|
||||||
<button type="submit">Manage Users</button>
|
<button type="submit">Manage Users</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
<div>
|
<div>
|
||||||
<form method="POST" action="/logout">
|
<form method="POST" action="/logout">
|
||||||
<input type="hidden" name="nonce" value="{{.Nonce}}">
|
<input type="hidden" name="nonce" value="{{.Nonce}}">
|
||||||
|
@ -58,15 +58,16 @@
|
|||||||
<label for="field_domain">Domain:</label>
|
<label for="field_domain">Domain:</label>
|
||||||
<input type="text" name="domain" id="field_domain" value="{{.Edit.Domain}}" required/>
|
<input type="text" name="domain" id="field_domain" value="{{.Edit.Domain}}" required/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="field_public">Public: <input type="checkbox" name="public" id="field_public" {{if .Edit.Public}}checked{{end}}/></label>
|
||||||
|
</div>
|
||||||
{{if .IsAdmin}}
|
{{if .IsAdmin}}
|
||||||
<div>
|
<div>
|
||||||
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso"
|
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso" {{if .Edit.SSO}}checked{{end}}/></label>
|
||||||
{{if .Edit.SSO}}checked{{end}}/></label>
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div>
|
<div>
|
||||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
|
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active" {{if .Edit.Active}}checked{{end}}/></label>
|
||||||
{{if .Edit.Active}}checked{{end}}/></label>
|
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">Edit</button>
|
<button type="submit">Edit</button>
|
||||||
</form>
|
</form>
|
||||||
@ -131,14 +132,16 @@
|
|||||||
<label for="field_domain">Domain:</label>
|
<label for="field_domain">Domain:</label>
|
||||||
<input type="text" name="domain" id="field_domain" required/>
|
<input type="text" name="domain" id="field_domain" required/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="field_public">Public: <input type="checkbox" name="public" id="field_public"/></label>
|
||||||
|
</div>
|
||||||
{{if .IsAdmin}}
|
{{if .IsAdmin}}
|
||||||
<div>
|
<div>
|
||||||
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso"/></label>
|
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso"/></label>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div>
|
<div>
|
||||||
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
|
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active" checked/></label>
|
||||||
checked/></label>
|
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">Create</button>
|
<button type="submit">Create</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1f349/lavender/database"
|
"github.com/1f349/lavender/database"
|
||||||
"github.com/go-session/session"
|
"github.com/go-session/session"
|
||||||
@ -24,7 +20,7 @@ type UserAuth struct {
|
|||||||
type SessionData struct {
|
type SessionData struct {
|
||||||
ID string
|
ID string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
UserInfo map[string]any
|
UserInfo UserInfoFields
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u UserAuth) IsGuest() bool {
|
func (u UserAuth) IsGuest() bool {
|
||||||
@ -45,7 +41,7 @@ func (h *HttpServer) RequireAdminAuthentication(next UserHandler) httprouter.Han
|
|||||||
}) {
|
}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if HasRole(roles, "lavender:admin") {
|
if !HasRole(roles, "lavender:admin") {
|
||||||
http.Error(rw, "403 Forbidden", http.StatusForbidden)
|
http.Error(rw, "403 Forbidden", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -71,14 +67,8 @@ func (h *HttpServer) OptionalAuthentication(next UserHandler) httprouter.Handle
|
|||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if auth.IsGuest() {
|
if auth.IsGuest() && !h.readLoginDataCookie(rw, req, &auth) {
|
||||||
if loginCookie, err := req.Cookie("login-data"); err == nil {
|
return
|
||||||
if decryptedBytes, err := base64.RawStdEncoding.DecodeString(loginCookie.Value); err == nil {
|
|
||||||
if decryptedData, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, h.signingKey.PrivateKey(), decryptedBytes, []byte("login-data")); err == nil {
|
|
||||||
auth.Data.ID = string(decryptedData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
next(rw, req, params, auth)
|
next(rw, req, params, auth)
|
||||||
}
|
}
|
||||||
|
126
server/login.go
126
server/login.go
@ -1,12 +1,17 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"database/sql"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/1f349/lavender/database"
|
||||||
|
"github.com/1f349/lavender/issuer"
|
||||||
"github.com/1f349/lavender/pages"
|
"github.com/1f349/lavender/pages"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
@ -106,61 +111,52 @@ func (h *HttpServer) loginCallback(rw http.ResponseWriter, req *http.Request, _
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := flowState.sso.OAuth2Config.Client(context.Background(), token).Get(flowState.sso.UserInfoEndpoint)
|
sessionData, done := h.fetchUserInfo(rw, err, flowState.sso, token)
|
||||||
if err != nil || res.StatusCode != 200 {
|
if !done {
|
||||||
rw.WriteHeader(http.StatusInternalServerError)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = rw.Write([]byte(err.Error()))
|
|
||||||
} else {
|
|
||||||
_, _ = rw.Write([]byte(res.Status))
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
var userInfoJson map[string]any
|
if h.DbTx(rw, func(tx *database.Tx) error {
|
||||||
if err := json.NewDecoder(res.Body).Decode(&userInfoJson); err != nil {
|
_, err := tx.GetUser(sessionData.ID)
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return
|
uEmail := sessionData.UserInfo.GetStringOrDefault("email", "unknown@localhost")
|
||||||
|
uEmailVerified, _ := sessionData.UserInfo.GetBoolean("email_verified")
|
||||||
|
return tx.InsertUser(sessionData.ID, uEmail, uEmailVerified, "", true)
|
||||||
}
|
}
|
||||||
subject, ok := userInfoJson["sub"].(string)
|
return err
|
||||||
if !ok {
|
}) {
|
||||||
http.Error(rw, "Invalid subject", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
subject += "@" + flowState.sso.Config.Namespace
|
|
||||||
|
|
||||||
displayName, ok := userInfoJson["name"].(string)
|
|
||||||
if !ok {
|
|
||||||
displayName = "Unknown Name"
|
|
||||||
}
|
|
||||||
|
|
||||||
// only continues if the above tx succeeds
|
// only continues if the above tx succeeds
|
||||||
auth.Data = SessionData{
|
auth.Data = sessionData
|
||||||
ID: subject,
|
|
||||||
DisplayName: displayName,
|
|
||||||
UserInfo: userInfoJson,
|
|
||||||
}
|
|
||||||
if auth.SaveSessionData() != nil {
|
if auth.SaveSessionData() != nil {
|
||||||
http.Error(rw, "Failed to save session", http.StatusInternalServerError)
|
http.Error(rw, "Failed to save session", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.setLoginDataCookie(rw, auth.Data.ID) {
|
if h.setLoginDataCookie(rw, auth.Data.ID, token) {
|
||||||
http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.SafeRedirect(rw, req)
|
h.SafeRedirect(rw, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HttpServer) setLoginDataCookie(rw http.ResponseWriter, userId string) bool {
|
func (h *HttpServer) setLoginDataCookie(rw http.ResponseWriter, userId string, token *oauth2.Token) bool {
|
||||||
encryptedData, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, h.signingKey.PublicKey(), []byte(userId), []byte("login-data"))
|
buf := new(bytes.Buffer)
|
||||||
|
buf.WriteString(userId)
|
||||||
|
buf.WriteByte(0)
|
||||||
|
err := json.NewEncoder(buf).Encode(token)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
encryptedData, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, h.signingKey.PublicKey(), buf.Bytes(), []byte("lavender-login-data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
encryptedString := base64.RawStdEncoding.EncodeToString(encryptedData)
|
encryptedString := base64.RawStdEncoding.EncodeToString(encryptedData)
|
||||||
http.SetCookie(rw, &http.Cookie{
|
http.SetCookie(rw, &http.Cookie{
|
||||||
Name: "login-data",
|
Name: "lavender-login-data",
|
||||||
Value: encryptedString,
|
Value: encryptedString,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Expires: time.Now().AddDate(0, 3, 0),
|
Expires: time.Now().AddDate(0, 3, 0),
|
||||||
@ -169,3 +165,71 @@ func (h *HttpServer) setLoginDataCookie(rw http.ResponseWriter, userId string) b
|
|||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *HttpServer) readLoginDataCookie(rw http.ResponseWriter, req *http.Request, u *UserAuth) bool {
|
||||||
|
loginCookie, err := req.Cookie("lavender-login-data")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
decryptedBytes, err := base64.RawStdEncoding.DecodeString(loginCookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
decryptedData, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, h.signingKey.PrivateKey(), decryptedBytes, []byte("lavender-login-data"))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(decryptedData)
|
||||||
|
userId, err := buf.ReadString(0)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
userId = strings.TrimSuffix(userId, "\x00")
|
||||||
|
|
||||||
|
var token *oauth2.Token
|
||||||
|
err = json.NewDecoder(buf).Decode(&token)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sso := h.manager.FindServiceFromLogin(userId)
|
||||||
|
if sso == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionData, done := h.fetchUserInfo(rw, err, sso, token)
|
||||||
|
if !done {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Data = sessionData
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpServer) fetchUserInfo(rw http.ResponseWriter, err error, sso *issuer.WellKnownOIDC, token *oauth2.Token) (SessionData, bool) {
|
||||||
|
res, err := sso.OAuth2Config.Client(context.Background(), token).Get(sso.UserInfoEndpoint)
|
||||||
|
if err != nil || res.StatusCode != http.StatusOK {
|
||||||
|
return SessionData{}, false
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
subject, ok := userInfoJson.GetString("sub")
|
||||||
|
if !ok {
|
||||||
|
http.Error(rw, "Invalid subject", http.StatusInternalServerError)
|
||||||
|
return SessionData{}, false
|
||||||
|
}
|
||||||
|
subject += "@" + sso.Config.Namespace
|
||||||
|
|
||||||
|
displayName := userInfoJson.GetStringOrDefault("name", "Unknown Name")
|
||||||
|
return SessionData{
|
||||||
|
ID: subject,
|
||||||
|
DisplayName: displayName,
|
||||||
|
UserInfo: userInfoJson,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
@ -30,7 +30,7 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
appList, err = tx.GetAppList(offset)
|
appList, err = tx.GetAppList(auth.Data.ID, HasRole(roles, "lavender:admin"), offset)
|
||||||
return
|
return
|
||||||
}) {
|
}) {
|
||||||
return
|
return
|
||||||
@ -72,6 +72,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
|
|||||||
action := req.Form.Get("action")
|
action := req.Form.Get("action")
|
||||||
name := req.Form.Get("name")
|
name := req.Form.Get("name")
|
||||||
domain := req.Form.Get("domain")
|
domain := req.Form.Get("domain")
|
||||||
|
public := req.Form.Has("public")
|
||||||
sso := req.Form.Has("sso")
|
sso := req.Form.Has("sso")
|
||||||
active := req.Form.Has("active")
|
active := req.Form.Has("active")
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
|
|||||||
switch action {
|
switch action {
|
||||||
case "create":
|
case "create":
|
||||||
if h.DbTx(rw, func(tx *database.Tx) error {
|
if h.DbTx(rw, func(tx *database.Tx) error {
|
||||||
return tx.InsertClientApp(name, domain, sso, active, auth.Data.ID)
|
return tx.InsertClientApp(name, domain, public, sso, active, auth.Data.ID)
|
||||||
}) {
|
}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -102,7 +103,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return tx.UpdateClientApp(sub, auth.Data.ID, name, domain, sso, active)
|
return tx.UpdateClientApp(sub, auth.Data.ID, name, domain, public, sso, active)
|
||||||
}) {
|
}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
http.SetCookie(rw, &http.Cookie{
|
http.SetCookie(rw, &http.Cookie{
|
||||||
Name: "login-data",
|
Name: "lavender-login-data",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
Secure: true,
|
Secure: true,
|
||||||
@ -156,8 +156,8 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser
|
|||||||
})
|
})
|
||||||
|
|
||||||
// management pages
|
// management pages
|
||||||
r.GET("/manage/apps", hs.RequireAdminAuthentication(hs.ManageAppsGet))
|
r.GET("/manage/apps", hs.RequireAuthentication(hs.ManageAppsGet))
|
||||||
r.POST("/manage/apps", hs.RequireAdminAuthentication(hs.ManageAppsPost))
|
r.POST("/manage/apps", hs.RequireAuthentication(hs.ManageAppsPost))
|
||||||
r.GET("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersGet))
|
r.GET("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersGet))
|
||||||
r.POST("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersPost))
|
r.POST("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersPost))
|
||||||
|
|
||||||
|
21
server/userinfo.go
Normal file
21
server/userinfo.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
type UserInfoFields map[string]any
|
||||||
|
|
||||||
|
func (u UserInfoFields) GetString(key string) (string, bool) {
|
||||||
|
s, ok := u[key].(string)
|
||||||
|
return s, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UserInfoFields) GetStringOrDefault(key, other string) string {
|
||||||
|
s, ok := u.GetString(key)
|
||||||
|
if !ok {
|
||||||
|
s = other
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UserInfoFields) GetBoolean(key string) (bool, bool) {
|
||||||
|
b, ok := u[key].(bool)
|
||||||
|
return b, ok
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user