Add support for public oauth client apps

This commit is contained in:
Melon 2024-02-08 01:16:46 +00:00
parent 33faf6aa5f
commit e822172513
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
9 changed files with 40 additions and 30 deletions

View File

@ -105,7 +105,7 @@ func (u *UserPatch) ParseFromForm(v url.Values) (safeErrs []error) {
type ClientInfoDbOutput struct {
Sub, Name, Secret, Domain, Owner string
SSO, Active bool
Public, SSO, Active bool
}
var _ oauth2.ClientInfo = &ClientInfoDbOutput{}
@ -113,7 +113,7 @@ var _ oauth2.ClientInfo = &ClientInfoDbOutput{}
func (c *ClientInfoDbOutput) GetID() string { return c.Sub }
func (c *ClientInfoDbOutput) GetSecret() string { return c.Secret }
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 }
// GetName is an extra field for the oauth handler to display the application

View File

@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS client_store
secret TEXT UNIQUE NOT NULL,
domain TEXT NOT NULL,
owner TEXT NOT NULL,
public INTEGER,
sso INTEGER,
active INTEGER DEFAULT 1,
FOREIGN KEY (owner) REFERENCES users (subject)

View File

@ -198,8 +198,8 @@ func (t *Tx) HasTwoFactor(sub uuid.UUID) (bool, error) {
func (t *Tx) GetClientInfo(sub string) (oauth2.ClientInfo, error) {
var u ClientInfoDbOutput
row := t.tx.QueryRow(`SELECT secret, name, domain, sso, active FROM client_store WHERE subject = ? LIMIT 1`, sub)
err := row.Scan(&u.Secret, &u.Name, &u.Domain, &u.SSO, &u.Active)
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.Public, &u.SSO, &u.Active)
u.Owner = sub
if !u.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
}
func (t *Tx) GetAppList(offset int) ([]ClientInfoDbOutput, error) {
func (t *Tx) GetAppList(owner uuid.UUID, admin bool, offset int) ([]ClientInfoDbOutput, error) {
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 {
return nil, err
}
defer row.Close()
for row.Next() {
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 {
return nil, err
}
@ -225,18 +225,18 @@ func (t *Tx) GetAppList(offset int) ([]ClientInfoDbOutput, error) {
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()
secret, err := password.GenerateApiSecret(70)
if err != nil {
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
}
func (t *Tx) UpdateClientApp(subject, owner uuid.UUID, name, domain string, 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())
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 = ?, public = ?, sso = ?, active = ? WHERE subject = ? AND owner = ?`, name, domain, public, sso, active, subject.String(), owner.String())
return err
}

View File

@ -25,11 +25,13 @@
<button type="submit">Manage Applications</button>
</form>
</div>
<div>
<form method="GET" action="/manage/users">
<button type="submit">Manage Users</button>
</form>
</div>
{{if .IsAdmin}}
<div>
<form method="GET" action="/manage/users">
<button type="submit">Manage Users</button>
</form>
</div>
{{end}}
{{if .OtpEnabled}}
<div>
<form method="POST" action="/edit/otp">

View File

@ -58,15 +58,16 @@
<label for="field_domain">Domain:</label>
<input type="text" name="domain" id="field_domain" value="{{.Edit.Domain}}" required/>
</div>
<div>
<label for="field_public">Public: <input type="checkbox" name="public" id="field_public" {{if .Edit.Public}}checked{{end}}/></label>
</div>
{{if .IsAdmin}}
<div>
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso"
{{if .Edit.SSO}}checked{{end}}/></label>
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso" {{if .Edit.SSO}}checked{{end}}/></label>
</div>
{{end}}
<div>
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active"
{{if .Edit.Active}}checked{{end}}/></label>
<label for="field_active">Active: <input type="checkbox" name="active" id="field_active" {{if .Edit.Active}}checked{{end}}/></label>
</div>
<button type="submit">Edit</button>
</form>
@ -85,6 +86,7 @@
<th>ID</th>
<th>Name</th>
<th>Domain</th>
<th>Public</th>
<th>SSO</th>
<th>Active</th>
<th>Owner</th>
@ -97,6 +99,7 @@
<td>{{.Sub}}</td>
<td>{{.Name}}</td>
<td>{{.Domain}}</td>
<td>{{.Public}}</td>
<td>{{.SSO}}</td>
<td>{{.Active}}</td>
<td>{{.Owner}}</td>
@ -131,6 +134,9 @@
<label for="field_domain">Domain:</label>
<input type="text" name="domain" id="field_domain" required/>
</div>
<div>
<label for="field_public">Public: <input type="checkbox" name="public" id="field_public"/></label>
</div>
{{if .IsAdmin}}
<div>
<label for="field_sso">SSO: <input type="checkbox" name="sso" id="field_sso"/></label>

View File

@ -83,9 +83,9 @@ func (h *HttpServer) OptionalAuthentication(flowPart bool, next UserHandler) htt
return
}
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 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 {
var u uuid.UUID
copy(u[:], decryptedData[:])

View File

@ -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 {
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 {
return true
}
encryptedString := base64.RawStdEncoding.EncodeToString(encryptedData)
http.SetCookie(rw, &http.Cookie{
Name: "login-data",
Name: "tulip-login-data",
Value: encryptedString,
Path: "/",
Expires: time.Now().AddDate(0, 3, 0),

View File

@ -30,7 +30,7 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _
if err != nil {
return
}
appList, err = tx.GetAppList(offset)
appList, err = tx.GetAppList(auth.Data.ID, role == database.RoleAdmin, offset)
return
}) {
return
@ -72,6 +72,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
action := req.Form.Get("action")
name := req.Form.Get("name")
domain := req.Form.Get("domain")
public := req.Form.Has("public")
sso := req.Form.Has("sso")
active := req.Form.Has("active")
@ -92,7 +93,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
switch action {
case "create":
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
}
@ -102,7 +103,7 @@ func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _
if err != nil {
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
}

View File

@ -137,7 +137,7 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser
}
http.SetCookie(rw, &http.Cookie{
Name: "login-data",
Name: "tulip-login-data",
Path: "/",
MaxAge: -1,
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))
// management pages
r.GET("/manage/apps", hs.RequireAdminAuthentication(hs.ManageAppsGet))
r.POST("/manage/apps", hs.RequireAdminAuthentication(hs.ManageAppsPost))
r.GET("/manage/apps", hs.RequireAuthentication(hs.ManageAppsGet))
r.POST("/manage/apps", hs.RequireAuthentication(hs.ManageAppsPost))
r.GET("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersGet))
r.POST("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersPost))