Mostly finish docs

Actually start to parse things
This commit is contained in:
Caleb Gardner
2024-05-19 06:47:41 -05:00
parent f7bbdaa4b3
commit 4244b6985a
11 changed files with 270 additions and 20 deletions
+34 -3
View File
@@ -15,11 +15,22 @@ This is a purposefully "simple" application backend made specifically for _my_ a
user: true, // create and login users
log: true, // log users
crash: true, // crash reports
management: false, // managing
// further permissions can be added as needed
}
}
```
### DB Log
```json
{
id: "UUID",
platform: "android",
Date: 20240519 // YYYYMMD
}
```
### User
Users are stored per backend and not per app.
@@ -88,6 +99,14 @@ If an error status code is returned then the body will be as follows.
}
```
### Log
API Key must have the `log` permission.
Request:
> POST: /log
### Users
> TODO: Add the ability to create users and log-in through third-parties (such as Google).
@@ -107,7 +126,7 @@ Request:
```json
{
username: "Username",
password: "Password", // Password must be
password: "Password", // Allowed length: 12-128
email: "Email",
}
```
@@ -158,11 +177,15 @@ Return:
### Crash Report
Crash reports require the `X-API-Key` header and the key must match the URL's appID and have the `crash` permission
> TODO: Archive a crash to prevent it being reported again.
#### Report
API Key must have the `crash` permission.
Request:
> POST: /{appID}/crash
> POST: /crash
```json
{
@@ -172,3 +195,11 @@ Request:
stack: "stacktrace"
}
```
#### Delete
API Key must have the `management` permission.
Request:
> DELETE: /crash/{crashID}
+2 -7
View File
@@ -1,11 +1,6 @@
package darkstorm
type App interface {
//TODO
}
type CrashApp interface {
App
AddCrash(CrashReport)
// TODO
LogTable() Table[Log]
CrashTable() CrashTable
}
+39
View File
@@ -1,5 +1,7 @@
package darkstorm
import "net/http"
type IndividualCrash struct {
Platform string
Error string
@@ -13,3 +15,40 @@ type CrashReport struct {
FirstLine string
Individual []IndividualCrash
}
func (c CrashReport) GetID() string {
return c.ID
}
type crashReq struct {
ID string
Platform string
Error string
Stack string
}
func (b *Backend) ReportCrash(w http.ResponseWriter, r *http.Request) {
hdr, err := b.ParseHeader(r)
if hdr.k == nil || hdr.k.Perm["crash"] {
w.WriteHeader(http.StatusUnauthorized)
return
}
if err != nil {
//TODO
return
}
//TODO
}
func (b *Backend) DeleteCrash(w http.ResponseWriter, r *http.Request) {
hdr, err := b.ParseHeader(r)
if hdr.k == nil || hdr.k.Perm["management"] {
w.WriteHeader(http.StatusUnauthorized)
return
}
if err != nil {
//TODO
return
}
//TODO
}
+85 -2
View File
@@ -1,7 +1,90 @@
package darkstorm
import "net/http"
import (
"crypto/ed25519"
"errors"
"net/http"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
)
var (
ErrApiKeyUnauthorized = errors.New("api key invalid")
ErrTokenUnauthorized = errors.New("token invalid")
)
type Backend struct {
http.ServeMux
userTable Table[User]
keyTable Table[Key]
m *http.ServeMux
jwtPriv ed25519.PrivateKey
jwtPub ed25519.PublicKey
apps []App
}
func NewBackend(keyTable Table[Key], apps ...App) (*Backend, error) {
b := &Backend{
keyTable: keyTable,
m: &http.ServeMux{},
apps: apps,
}
//TODO: register paths to the mux
b.startCleanupLoop()
return b, nil
}
func (b *Backend) AddUserAuth(userTable Table[User], privKey, pubKey []byte) {
b.userTable = userTable
b.jwtPriv = privKey
b.jwtPub = pubKey
}
func (b *Backend) HandleFunc(pattern string, h http.HandlerFunc) {
b.m.HandleFunc(pattern, h)
}
func (b *Backend) startCleanupLoop() {
go func() {
for range time.Tick(6 * time.Hour) {
//TODO
}
}()
}
type ParsedHeader struct {
u *ReqUser
k *Key
}
func (b *Backend) ParseHeader(r *http.Request) (ParsedHeader, error) {
out := ParsedHeader{}
key := r.Header.Get("X-API-Key")
token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
if key != "" {
apiKey, err := b.keyTable.Get(key)
if err != nil {
return out, errors.Join(ErrApiKeyUnauthorized, err)
}
out.k = &apiKey
}
if token != "" && b.userTable != nil {
t, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
return b.jwtPub, nil
}, jwt.WithIssuer("darkstorm.tech"), jwt.WithExpirationRequired())
if err != nil {
return out, errors.Join(ErrTokenUnauthorized, err)
}
sub, err := t.Claims.GetSubject()
if err != nil {
return out, errors.Join(ErrTokenUnauthorized, err)
}
usr, err := b.userTable.Get(sub)
if err != nil{
return out, errors.Join(ErrTokenUnauthorized, err)
}
}
return out, nil
}
+23
View File
@@ -0,0 +1,23 @@
package darkstorm
import "errors"
var (
ErrIDNotFound = errors.New("id not found in table")
)
type IDStruct interface {
GetID() string
}
type Table[T IDStruct] interface {
Get(ID string) (data T, err error)
Insert(data T) error
Update(data T) error
Remove(ID string)
}
type CrashTable interface {
Table[CrashReport]
InsertCrash(report IndividualCrash) error
}
+12
View File
@@ -0,0 +1,12 @@
package darkstorm
type Key struct {
Perm map[string]bool
ID string
AppID string
Death int
}
func (k Key) GetID() string {
return k.ID
}
+11
View File
@@ -0,0 +1,11 @@
package darkstorm
type Log struct {
ID string
Platform string
Date int
}
func (l Log) GetID() string {
return l.ID
}
+58 -2
View File
@@ -3,11 +3,17 @@ package darkstorm
import (
"crypto/rand"
"encoding/base64"
"errors"
"net/http"
"github.com/google/uuid"
"golang.org/x/crypto/argon2"
)
var (
ErrPasswordLength = errors.New("password length must be 12-128")
)
func generateSalt() (string, error) {
out := make([]byte, 16)
_, err := rand.Read(out)
@@ -24,7 +30,16 @@ type User struct {
PasswordChange uint64
}
type ReqUser struct {
Perm map[string]string
ID string
Username string
}
func NewUser(username, password, email string) (*User, error) {
if len(password) < 12 || len(password) > 128 {
return nil, ErrPasswordLength
}
id, err := uuid.NewV7()
if err != nil {
return nil, err
@@ -44,7 +59,19 @@ func NewUser(username, password, email string) (*User, error) {
return out, err
}
func (u *User) HashPassword(password string) (string, error) {
func (u User) GetID() string {
return u.ID
}
func (u User) toReqUser() ReqUser {
return ReqUser{
Perm: u.Perm,
ID: u.ID,
Username: u.Username,
}
}
func (u User) HashPassword(password string) (string, error) {
salt, err := base64.RawStdEncoding.DecodeString(u.Salt)
if err != nil {
return "", err
@@ -53,10 +80,39 @@ func (u *User) HashPassword(password string) (string, error) {
return base64.RawStdEncoding.EncodeToString(res), nil
}
func (u *User) ValidatePassword(password string) (bool, error) {
func (u User) ValidatePassword(password string) (bool, error) {
hsh, err := u.HashPassword(password)
if err != nil {
return false, err
}
return hsh == u.Password, nil
}
type createUserRequest struct {
Username string
Password string
Email string
}
type createUserReturn struct {
Username string
Token string
}
func (b *Backend) CreateUser(w http.ResponseWriter, r *http.Request) {
//TODO
}
type loginRequest struct {
Username string
Password string
}
type loginReturn struct {
Token string
Timeout int
}
func (b *Backend) Login(w http.ResponseWriter, r *http.Request) {
//TODO
}
-5
View File
@@ -1,5 +0,0 @@
package db
type DB interface {
//TODO
}