Mostly finish docs
Actually start to parse things
This commit is contained in:
@@ -7,4 +7,7 @@ require (
|
|||||||
golang.org/x/crypto v0.23.0
|
golang.org/x/crypto v0.23.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.20.0 // indirect
|
require (
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
|
|||||||
@@ -15,11 +15,22 @@ This is a purposefully "simple" application backend made specifically for _my_ a
|
|||||||
user: true, // create and login users
|
user: true, // create and login users
|
||||||
log: true, // log users
|
log: true, // log users
|
||||||
crash: true, // crash reports
|
crash: true, // crash reports
|
||||||
|
management: false, // managing
|
||||||
// further permissions can be added as needed
|
// further permissions can be added as needed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### DB Log
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
id: "UUID",
|
||||||
|
platform: "android",
|
||||||
|
Date: 20240519 // YYYYMMD
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### User
|
### User
|
||||||
|
|
||||||
Users are stored per backend and not per app.
|
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
|
### Users
|
||||||
|
|
||||||
> TODO: Add the ability to create users and log-in through third-parties (such as Google).
|
> TODO: Add the ability to create users and log-in through third-parties (such as Google).
|
||||||
@@ -107,7 +126,7 @@ Request:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
username: "Username",
|
username: "Username",
|
||||||
password: "Password", // Password must be
|
password: "Password", // Allowed length: 12-128
|
||||||
email: "Email",
|
email: "Email",
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -158,11 +177,15 @@ Return:
|
|||||||
|
|
||||||
### Crash Report
|
### 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:
|
Request:
|
||||||
|
|
||||||
> POST: /{appID}/crash
|
> POST: /crash
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -172,3 +195,11 @@ Request:
|
|||||||
stack: "stacktrace"
|
stack: "stacktrace"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Delete
|
||||||
|
|
||||||
|
API Key must have the `management` permission.
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
> DELETE: /crash/{crashID}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
package darkstorm
|
package darkstorm
|
||||||
|
|
||||||
type App interface {
|
type App interface {
|
||||||
//TODO
|
LogTable() Table[Log]
|
||||||
}
|
CrashTable() CrashTable
|
||||||
|
|
||||||
type CrashApp interface {
|
|
||||||
App
|
|
||||||
AddCrash(CrashReport)
|
|
||||||
// TODO
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package darkstorm
|
package darkstorm
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
type IndividualCrash struct {
|
type IndividualCrash struct {
|
||||||
Platform string
|
Platform string
|
||||||
Error string
|
Error string
|
||||||
@@ -13,3 +15,40 @@ type CrashReport struct {
|
|||||||
FirstLine string
|
FirstLine string
|
||||||
Individual []IndividualCrash
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,90 @@
|
|||||||
package darkstorm
|
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 {
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package darkstorm
|
||||||
|
|
||||||
|
type Log struct {
|
||||||
|
ID string
|
||||||
|
Platform string
|
||||||
|
Date int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Log) GetID() string {
|
||||||
|
return l.ID
|
||||||
|
}
|
||||||
@@ -3,11 +3,17 @@ package darkstorm
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrPasswordLength = errors.New("password length must be 12-128")
|
||||||
|
)
|
||||||
|
|
||||||
func generateSalt() (string, error) {
|
func generateSalt() (string, error) {
|
||||||
out := make([]byte, 16)
|
out := make([]byte, 16)
|
||||||
_, err := rand.Read(out)
|
_, err := rand.Read(out)
|
||||||
@@ -24,7 +30,16 @@ type User struct {
|
|||||||
PasswordChange uint64
|
PasswordChange uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReqUser struct {
|
||||||
|
Perm map[string]string
|
||||||
|
ID string
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
func NewUser(username, password, email string) (*User, error) {
|
func NewUser(username, password, email string) (*User, error) {
|
||||||
|
if len(password) < 12 || len(password) > 128 {
|
||||||
|
return nil, ErrPasswordLength
|
||||||
|
}
|
||||||
id, err := uuid.NewV7()
|
id, err := uuid.NewV7()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -44,7 +59,19 @@ func NewUser(username, password, email string) (*User, error) {
|
|||||||
return out, err
|
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)
|
salt, err := base64.RawStdEncoding.DecodeString(u.Salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -53,10 +80,39 @@ func (u *User) HashPassword(password string) (string, error) {
|
|||||||
return base64.RawStdEncoding.EncodeToString(res), nil
|
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)
|
hsh, err := u.HashPassword(password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return hsh == u.Password, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
type DB interface {
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user