mirror of
https://github.com/1f349/tulip.git
synced 2024-12-23 00:34:07 +00:00
Add support for public oauth client apps
This commit is contained in:
parent
33faf6aa5f
commit
e822172513
@ -105,7 +105,7 @@ func (u *UserPatch) ParseFromForm(v url.Values) (safeErrs []error) {
|
|||||||
|
|
||||||
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{}
|
||||||
@ -113,7 +113,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
|
||||||
|
@ -27,6 +27,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)
|
||||||
|
@ -198,8 +198,8 @@ func (t *Tx) HasTwoFactor(sub uuid.UUID) (bool, 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")
|
||||||
@ -207,16 +207,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 uuid.UUID, 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.String(), 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
|
||||||
}
|
}
|
||||||
@ -225,18 +225,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 uuid.UUID) error {
|
func (t *Tx) InsertClientApp(name, domain string, public, sso, active bool, owner uuid.UUID) 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.String(), 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.String(), public, sso, active)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tx) UpdateClientApp(subject, owner uuid.UUID, name, domain string, sso, active bool) error {
|
func (t *Tx) UpdateClientApp(subject, owner uuid.UUID, 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.String())
|
_, 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.String())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,11 +25,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}}
|
||||||
{{if .OtpEnabled}}
|
{{if .OtpEnabled}}
|
||||||
<div>
|
<div>
|
||||||
<form method="POST" action="/edit/otp">
|
<form method="POST" action="/edit/otp">
|
||||||
|
@ -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>
|
||||||
@ -85,6 +86,7 @@
|
|||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Domain</th>
|
<th>Domain</th>
|
||||||
|
<th>Public</th>
|
||||||
<th>SSO</th>
|
<th>SSO</th>
|
||||||
<th>Active</th>
|
<th>Active</th>
|
||||||
<th>Owner</th>
|
<th>Owner</th>
|
||||||
@ -97,6 +99,7 @@
|
|||||||
<td>{{.Sub}}</td>
|
<td>{{.Sub}}</td>
|
||||||
<td>{{.Name}}</td>
|
<td>{{.Name}}</td>
|
||||||
<td>{{.Domain}}</td>
|
<td>{{.Domain}}</td>
|
||||||
|
<td>{{.Public}}</td>
|
||||||
<td>{{.SSO}}</td>
|
<td>{{.SSO}}</td>
|
||||||
<td>{{.Active}}</td>
|
<td>{{.Active}}</td>
|
||||||
<td>{{.Owner}}</td>
|
<td>{{.Owner}}</td>
|
||||||
@ -131,6 +134,9 @@
|
|||||||
<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>
|
||||||
|
@ -83,9 +83,9 @@ func (h *HttpServer) OptionalAuthentication(flowPart bool, next UserHandler) htt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if auth.IsGuest() {
|
if auth.IsGuest() {
|
||||||
if loginCookie, err := req.Cookie("login-data"); err == nil {
|
if loginCookie, err := req.Cookie("tulip-login-data"); err == nil {
|
||||||
if decryptedBytes, err := base64.RawStdEncoding.DecodeString(loginCookie.Value); err == nil {
|
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 {
|
if decryptedData, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, h.signingKey.PrivateKey(), decryptedBytes, []byte("tulip-login-data")); err == nil {
|
||||||
if len(decryptedData) == 16 {
|
if len(decryptedData) == 16 {
|
||||||
var u uuid.UUID
|
var u uuid.UUID
|
||||||
copy(u[:], decryptedData[:])
|
copy(u[:], decryptedData[:])
|
||||||
|
@ -150,13 +150,13 @@ func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ http
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *HttpServer) setLoginDataCookie(rw http.ResponseWriter, userId uuid.UUID) bool {
|
func (h *HttpServer) setLoginDataCookie(rw http.ResponseWriter, userId uuid.UUID) bool {
|
||||||
encryptedData, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, h.signingKey.PublicKey(), userId[:], []byte("login-data"))
|
encryptedData, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, h.signingKey.PublicKey(), userId[:], []byte("tulip-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: "tulip-login-data",
|
||||||
Value: encryptedString,
|
Value: encryptedString,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Expires: time.Now().AddDate(0, 3, 0),
|
Expires: time.Now().AddDate(0, 3, 0),
|
||||||
|
@ -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, role == database.RoleAdmin, 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: "tulip-login-data",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
Secure: true,
|
Secure: true,
|
||||||
@ -173,8 +173,8 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser
|
|||||||
r.POST("/edit/otp", hs.RequireAuthentication(hs.EditOtpPost))
|
r.POST("/edit/otp", hs.RequireAuthentication(hs.EditOtpPost))
|
||||||
|
|
||||||
// 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))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user