From 308e237fc6d69ec25d70312f051dc805f9203fc1 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Fri, 9 May 2025 17:17:55 -0700 Subject: [PATCH] in the beginning, there was darkness --- README.org | 47 ++++++++++++++++++++++++++++++++++++++++ data/rand.go | 13 +++++++++++ data/user.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 5 +++++ go.sum | 3 +++ schema.sql | 29 +++++++++++++++++++++++++ 6 files changed, 158 insertions(+) create mode 100644 README.org create mode 100644 data/rand.go create mode 100644 data/user.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 schema.sql diff --git a/README.org b/README.org new file mode 100644 index 0000000..9913a35 --- /dev/null +++ b/README.org @@ -0,0 +1,47 @@ +#+title: MCIAS +#+created: <2025-05-09 Fri 13:42> + +* MCIAS + + MCIAS is the metacircular identity and access system. + + It currently provides the following across metacircular services: + + 1. User password authentication. + 2. User token authentication. + 3. Database credential authentication. + + Future work should consider adding support for: + 1. TOTP + 2. Policy management. + +** API endpoints + +*** The login type + + The general datastructure used to log in should look like: + +#+begin_src: json +{ + "version": "v1", + "login": { + "user": "username", + "password": "secret password", + "token": "1234567890", + "totp": "123456" + } +} +#+end_src + + Any fields that aren't used should be omitted. The =version= and + =login.user= types are required, as well as the appropriate + credential field. + +*** =/v1/login/password= + + The request should be a JSON object: + + +*** =/v1/login/token= + +*** =/v1/credentials/database= diff --git a/data/rand.go b/data/rand.go new file mode 100644 index 0000000..eeb34ae --- /dev/null +++ b/data/rand.go @@ -0,0 +1,13 @@ +package data + +const saltLength = 32 + +func Salt() ([]byte, error) { + salt := make([]byte, saltLength) + _, err := rand.Read(salt) + if err != nil { + return nil, err + } + + return salt, nil +} diff --git a/data/user.go b/data/user.go new file mode 100644 index 0000000..f5d75d6 --- /dev/null +++ b/data/user.go @@ -0,0 +1,61 @@ +package data + +const ( + scryptN = 32768 + scriptR = 8 + scryptP = 1 +) + +type User struct { + ID string + Created int64 + User string + Password []byte + Salt []byte +} + +type Login struct { + User string `json:"user"` + Password string `json:"password,omitzero"` + Token string `json:"token,omitzero"` +} + +func derive(password string, salt []byte) []byte { + return scrypt.Key(login.Password, u.Salt, scryptN, scryptR, scryptN, 32) +} + +func (u *User) Check(login *Login) bool { + if u.User != login.User { + return false + } + + derived := derive(login.Password, u.Salt) + + if subtle.ConstantTimeCompare(derived, u.Password) != 0 { + return false + } + + return true +} + +func (u *User) Register(login *Login) error { + var err error + + if u.User != "" && u.User != login.User { + return errors.New("invalid user") + } + + if u.ID == "" { + u.ID = ulid.Make() + } + + u.User = login.User + u.Salt, err = Salt() + if err != nil { + return fmt.Errorf("failed to register user: %w", err) + } + + u.Password = derive(login.Password, u.Salt) + return nil +} + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..542cd01 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.wntrmute.dev/kyle/mcias + +go 1.23.8 + +require github.com/oklog/ulid/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5a427a4 --- /dev/null +++ b/go.sum @@ -0,0 +1,3 @@ +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..17270ae --- /dev/null +++ b/schema.sql @@ -0,0 +1,29 @@ +CREATE TABLE users ( + id text primary key, + created integer, + user text not null, + password blob not null, + salt blob not null +); + +CREATE TABLE tokens ( + id text primary key, + uid text not null, + token text not null, + expires integer default 0, + FOREIGN KEY(uid) REFERENCES user(id) +); + +CREATE TABLE database ( + id text primary key, + host text not null, + port integer default 5432, + name text not null, + user text not null, + password text not null +); + +CREATE TABLE registrations ( + id text primary key, + code text not null +);