-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
abstract subscription, token, and user api
- Loading branch information
Showing
15 changed files
with
1,056 additions
and
779 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,298 @@ | ||
/** | ||
* OpenBmclAPI (Golang Edition) | ||
* Copyright (C) 2024 Kevin Z <[email protected]> | ||
* All rights reserved | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as published | ||
* by the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package api | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/google/uuid" | ||
|
||
"github.com/LiterMC/go-openbmclapi/utils" | ||
) | ||
|
||
type SubscriptionManager interface { | ||
GetWebPushKey() string | ||
|
||
GetSubscribe(user string, client string) (*SubscribeRecord, error) | ||
SetSubscribe(SubscribeRecord) error | ||
RemoveSubscribe(user string, client string) error | ||
ForEachSubscribe(cb func(*SubscribeRecord) error) error | ||
|
||
GetEmailSubscription(user string, addr string) (*EmailSubscriptionRecord, error) | ||
AddEmailSubscription(EmailSubscriptionRecord) error | ||
UpdateEmailSubscription(EmailSubscriptionRecord) error | ||
RemoveEmailSubscription(user string, addr string) error | ||
ForEachEmailSubscription(cb func(*EmailSubscriptionRecord) error) error | ||
ForEachUsersEmailSubscription(user string, cb func(*EmailSubscriptionRecord) error) error | ||
ForEachEnabledEmailSubscription(cb func(*EmailSubscriptionRecord) error) error | ||
|
||
GetWebhook(user string, id uuid.UUID) (*WebhookRecord, error) | ||
AddWebhook(WebhookRecord) error | ||
UpdateWebhook(WebhookRecord) error | ||
UpdateEnableWebhook(user string, id uuid.UUID, enabled bool) error | ||
RemoveWebhook(user string, id uuid.UUID) error | ||
ForEachWebhook(cb func(*WebhookRecord) error) error | ||
ForEachUsersWebhook(user string, cb func(*WebhookRecord) error) error | ||
ForEachEnabledWebhook(cb func(*WebhookRecord) error) error | ||
} | ||
|
||
type SubscribeRecord struct { | ||
User string `json:"user"` | ||
Client string `json:"client"` | ||
EndPoint string `json:"endpoint"` | ||
Keys SubscribeRecordKeys `json:"keys"` | ||
Scopes NotificationScopes `json:"scopes"` | ||
ReportAt Schedule `json:"report_at"` | ||
LastReport sql.NullTime `json:"-"` | ||
} | ||
|
||
type SubscribeRecordKeys struct { | ||
Auth string `json:"auth"` | ||
P256dh string `json:"p256dh"` | ||
} | ||
|
||
var ( | ||
_ sql.Scanner = (*SubscribeRecordKeys)(nil) | ||
_ driver.Valuer = (*SubscribeRecordKeys)(nil) | ||
) | ||
|
||
func (sk *SubscribeRecordKeys) Scan(src any) error { | ||
var data []byte | ||
switch v := src.(type) { | ||
case []byte: | ||
data = v | ||
case string: | ||
data = ([]byte)(v) | ||
default: | ||
return errors.New("Source is not a string") | ||
} | ||
return json.Unmarshal(data, sk) | ||
} | ||
|
||
func (sk SubscribeRecordKeys) Value() (driver.Value, error) { | ||
return json.Marshal(sk) | ||
} | ||
|
||
type NotificationScopes struct { | ||
Disabled bool `json:"disabled"` | ||
Enabled bool `json:"enabled"` | ||
SyncBegin bool `json:"syncbegin"` | ||
SyncDone bool `json:"syncdone"` | ||
Updates bool `json:"updates"` | ||
DailyReport bool `json:"dailyreport"` | ||
} | ||
|
||
var ( | ||
_ sql.Scanner = (*NotificationScopes)(nil) | ||
_ driver.Valuer = (*NotificationScopes)(nil) | ||
) | ||
|
||
//// !!WARN: Do not edit nsFlag's order //// | ||
|
||
const ( | ||
nsFlagDisabled = 1 << iota | ||
nsFlagEnabled | ||
nsFlagSyncDone | ||
nsFlagUpdates | ||
nsFlagDailyReport | ||
nsFlagSyncBegin | ||
) | ||
|
||
func (ns NotificationScopes) ToInt64() (v int64) { | ||
if ns.Disabled { | ||
v |= nsFlagDisabled | ||
} | ||
if ns.Enabled { | ||
v |= nsFlagEnabled | ||
} | ||
if ns.SyncBegin { | ||
v |= nsFlagSyncBegin | ||
} | ||
if ns.SyncDone { | ||
v |= nsFlagSyncDone | ||
} | ||
if ns.Updates { | ||
v |= nsFlagUpdates | ||
} | ||
if ns.DailyReport { | ||
v |= nsFlagDailyReport | ||
} | ||
return | ||
} | ||
|
||
func (ns *NotificationScopes) FromInt64(v int64) { | ||
ns.Disabled = v&nsFlagDisabled != 0 | ||
ns.Enabled = v&nsFlagEnabled != 0 | ||
ns.SyncBegin = v&nsFlagSyncBegin != 0 | ||
ns.SyncDone = v&nsFlagSyncDone != 0 | ||
ns.Updates = v&nsFlagUpdates != 0 | ||
ns.DailyReport = v&nsFlagDailyReport != 0 | ||
} | ||
|
||
func (ns *NotificationScopes) Scan(src any) error { | ||
v, ok := src.(int64) | ||
if !ok { | ||
return errors.New("Source is not a integer") | ||
} | ||
ns.FromInt64(v) | ||
return nil | ||
} | ||
|
||
func (ns NotificationScopes) Value() (driver.Value, error) { | ||
return ns.ToInt64(), nil | ||
} | ||
|
||
func (ns *NotificationScopes) FromStrings(scopes []string) { | ||
for _, s := range scopes { | ||
switch s { | ||
case "disabled": | ||
ns.Disabled = true | ||
case "enabled": | ||
ns.Enabled = true | ||
case "syncbegin": | ||
ns.SyncBegin = true | ||
case "syncdone": | ||
ns.SyncDone = true | ||
case "updates": | ||
ns.Updates = true | ||
case "dailyreport": | ||
ns.DailyReport = true | ||
} | ||
} | ||
} | ||
|
||
func (ns *NotificationScopes) UnmarshalJSON(data []byte) (err error) { | ||
{ | ||
type T NotificationScopes | ||
if err = json.Unmarshal(data, (*T)(ns)); err == nil { | ||
return | ||
} | ||
} | ||
var v []string | ||
if err = json.Unmarshal(data, &v); err != nil { | ||
return | ||
} | ||
ns.FromStrings(v) | ||
return | ||
} | ||
|
||
type Schedule struct { | ||
Hour int | ||
Minute int | ||
} | ||
|
||
var ( | ||
_ sql.Scanner = (*Schedule)(nil) | ||
_ driver.Valuer = (*Schedule)(nil) | ||
) | ||
|
||
func (s Schedule) String() string { | ||
return fmt.Sprintf("%02d:%02d", s.Hour, s.Minute) | ||
} | ||
|
||
func (s *Schedule) UnmarshalText(buf []byte) (err error) { | ||
if _, err = fmt.Sscanf((string)(buf), "%02d:%02d", &s.Hour, &s.Minute); err != nil { | ||
return | ||
} | ||
if s.Hour < 0 || s.Hour >= 24 { | ||
return fmt.Errorf("Hour %d out of range [0, 24)", s.Hour) | ||
} | ||
if s.Minute < 0 || s.Minute >= 60 { | ||
return fmt.Errorf("Minute %d out of range [0, 60)", s.Minute) | ||
} | ||
return | ||
} | ||
|
||
func (s *Schedule) UnmarshalJSON(buf []byte) (err error) { | ||
var v string | ||
if err = json.Unmarshal(buf, &v); err != nil { | ||
return | ||
} | ||
return s.UnmarshalText(([]byte)(v)) | ||
} | ||
|
||
func (s *Schedule) MarshalJSON() (buf []byte, err error) { | ||
return json.Marshal(s.String()) | ||
} | ||
|
||
func (s *Schedule) Scan(src any) error { | ||
var v []byte | ||
switch w := src.(type) { | ||
case []byte: | ||
v = w | ||
case string: | ||
v = ([]byte)(w) | ||
default: | ||
return fmt.Errorf("Unexpected type %T", src) | ||
} | ||
return s.UnmarshalText(v) | ||
} | ||
|
||
func (s Schedule) Value() (driver.Value, error) { | ||
return s.String(), nil | ||
} | ||
|
||
func (s Schedule) ReadySince(last, now time.Time) bool { | ||
if last.IsZero() { | ||
last = now.Add(-time.Hour*24 + 1) | ||
} | ||
mustAfter := last.Add(time.Hour * 12) | ||
if now.Before(mustAfter) { | ||
return false | ||
} | ||
if !now.Before(last.Add(time.Hour * 24)) { | ||
return true | ||
} | ||
hour, min := now.Hour(), now.Minute() | ||
if s.Hour < hour && s.Hour+3 > hour || s.Hour == hour && s.Minute <= min { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
type EmailSubscriptionRecord struct { | ||
User string `json:"user"` | ||
Addr string `json:"addr"` | ||
Scopes NotificationScopes `json:"scopes"` | ||
Enabled bool `json:"enabled"` | ||
} | ||
|
||
type WebhookRecord struct { | ||
User string `json:"user"` | ||
Id uuid.UUID `json:"id"` | ||
Name string `json:"name"` | ||
EndPoint string `json:"endpoint"` | ||
Auth *string `json:"auth,omitempty"` | ||
AuthHash string `json:"authHash,omitempty"` | ||
Scopes NotificationScopes `json:"scopes"` | ||
Enabled bool `json:"enabled"` | ||
} | ||
|
||
func (rec *WebhookRecord) CovertAuthHash() { | ||
if rec.Auth == nil || *rec.Auth == "" { | ||
rec.AuthHash = "" | ||
} else { | ||
rec.AuthHash = "sha256:" + utils.AsSha256Hex(*rec.Auth) | ||
} | ||
rec.Auth = nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/** | ||
* OpenBmclAPI (Golang Edition) | ||
* Copyright (C) 2024 Kevin Z <[email protected]> | ||
* All rights reserved | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as published | ||
* by the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package api | ||
|
||
import ( | ||
"net/url" | ||
) | ||
|
||
type TokenVerifier interface { | ||
VerifyAuthToken(clientId string, token string) (tokenId string, userId string, err error) | ||
VerifyAPIToken(clientId string, token string, path string, query url.Values) (userId string, err error) | ||
} | ||
|
||
type TokenManager interface { | ||
TokenVerifier | ||
GenerateAuthToken(clientId string, userId string) (token string, err error) | ||
GenerateAPIToken(clientId string, userId string, path string, query map[string]string) (token string, err error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* OpenBmclAPI (Golang Edition) | ||
* Copyright (C) 2024 Kevin Z <[email protected]> | ||
* All rights reserved | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as published | ||
* by the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package api | ||
|
||
type UserManager interface { | ||
GetUsers() []*User | ||
GetUser(id string) *User | ||
AddUser(*User) error | ||
RemoveUser(id string) error | ||
UpdateUserPassword(username string, password string) error | ||
UpdateUserPermissions(username string, permissions PermissionFlag) error | ||
|
||
VerifyUserPassword(userId string, comparator func(password string) bool) error | ||
} | ||
|
||
type PermissionFlag uint32 | ||
|
||
const ( | ||
// BasicPerm includes majority client side actions, such as login, which do not have a significant impact on the server | ||
BasicPerm PermissionFlag = 1 << iota | ||
// SubscribePerm allows the user to subscribe server status & other posts | ||
SubscribePerm | ||
// LogPerm allows the user to view non-debug logs & download access logs | ||
LogPerm | ||
// DebugPerm allows the user to access debug settings and download debug logs | ||
DebugPerm | ||
// FullConfigPerm allows the user to access all config values | ||
FullConfigPerm | ||
// ClusterPerm allows the user to configure clusters' settings & stop/start clusters | ||
ClusterPerm | ||
// StoragePerm allows the user to configure storages' settings & decides to manually start storages' sync process | ||
StoragePerm | ||
// BypassLimitPerm allows the user to ignore API access limit | ||
BypassLimitPerm | ||
// RootPerm user can add/remove users, reset their password, and change their permission flags | ||
RootPerm PermissionFlag = 1 << 31 | ||
|
||
AllPerm = ^(PermissionFlag)(0) | ||
) | ||
|
||
type User struct { | ||
Username string | ||
Password string // as sha256 | ||
Permissions PermissionFlag | ||
} |
Oops, something went wrong.