26 Commits
v1.0.0 ... main

Author SHA1 Message Date
37862e747f implement delete session 2025-02-14 10:59:51 -06:00
9bfc591fcc add revoke client approval 2025-02-13 13:00:21 -06:00
32facb1767 replace Id with ID 2025-02-13 08:00:49 -06:00
f948a06b66 add sessions 2025-02-13 07:37:06 -06:00
dkelly
e00a85b1a4 Merge pull request 'add oidc sqlite' (#1) from OIDCSqlite into main
Reviewed-on: https://git.infra.nkode.tech/dkelly/nkode-core/pulls/1
2025-02-13 10:57:07 +00:00
28bfbb84ad add oidc sqlite 2025-02-13 04:51:17 -06:00
6b8887832e add license 2025-02-12 06:39:51 -06:00
4e61d7714e rename structs 2025-02-09 09:38:02 -06:00
75d8250f72 cleanup code 2025-02-01 06:55:20 -06:00
f853b22b43 cleanup code 2025-02-01 06:54:40 -06:00
f421249943 remove user permission 2025-02-01 06:35:50 -06:00
6289faf1ba remove new customer endpoint 2025-02-01 06:33:43 -06:00
d58c69cb08 implement user_permission 2025-02-01 03:04:52 -06:00
72414bc8fb implement user_permission 2025-01-31 10:27:06 -06:00
9ee27f14cf implement and test forget and reset nkode 2025-01-31 10:26:30 -06:00
6dd84e4ca3 split sign and reset 2025-01-30 11:33:16 -06:00
597532bf26 implement gin nkode api 2025-01-27 02:54:35 -06:00
44bede14e4 refactor sqlite repository 2025-01-25 14:39:02 -06:00
0a1b8f9457 refactor var names 2025-01-25 14:30:06 -06:00
7f6c828ea5 don't use session 2025-01-23 14:54:05 -06:00
665341961d add delete session 2025-01-23 14:43:59 -06:00
d23671ff2b add delete session 2025-01-23 14:43:51 -06:00
c1ed5dafe3 add user login sessions 2025-01-23 14:42:34 -06:00
2eb72988e5 printf to log 2025-01-23 14:23:28 -06:00
536894c7dd fix make table order 2025-01-23 06:41:40 -06:00
3c0b3c04b7 implement cli 2025-01-23 06:33:29 -06:00
34 changed files with 1966 additions and 454 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
icons/ icons/
json_icon/ json_icon/
flaticon_colored_svgs/ flaticon_colored_svgs/
nkode

2
LICENSE Normal file
View File

@@ -0,0 +1,2 @@
This software is provided for personal and non-commercial use only.
Any commercial use, including but not limited to resale, incorporation into a commercial product, or use in a for-profit service, is strictly prohibited without prior written permission from the author.

View File

@@ -1,6 +1,17 @@
version: "3" version: "3"
vars:
test_db: "~/databases/test.db"
schema_db: "./sqlite/schema.sql"
svg_path: "~/svgs/flaticon_colored_svgs"
tasks: tasks:
sql: sqlc:
cmds: cmds:
- sqlc generate - sqlc generate
rebuild_test_db:
cmds:
- go build cmd/nkode/nkode.go
- rm {{.test_db}}
- sqlite3 {{.test_db}} < {{.schema_db}}
- ./nkode -db-path {{.test_db}} -svg-path {{.svg_path}}

View File

@@ -5,6 +5,7 @@ import (
"git.infra.nkode.tech/dkelly/nkode-core/config" "git.infra.nkode.tech/dkelly/nkode-core/config"
"git.infra.nkode.tech/dkelly/nkode-core/email" "git.infra.nkode.tech/dkelly/nkode-core/email"
"git.infra.nkode.tech/dkelly/nkode-core/entities" "git.infra.nkode.tech/dkelly/nkode-core/entities"
"git.infra.nkode.tech/dkelly/nkode-core/memcache"
"git.infra.nkode.tech/dkelly/nkode-core/repository" "git.infra.nkode.tech/dkelly/nkode-core/repository"
"git.infra.nkode.tech/dkelly/nkode-core/security" "git.infra.nkode.tech/dkelly/nkode-core/security"
"github.com/google/uuid" "github.com/google/uuid"
@@ -20,37 +21,48 @@ const (
) )
type NKodeAPI struct { type NKodeAPI struct {
Db repository.CustomerUserRepository repo repository.CustomerUserRepository
SignupSessionCache *cache.Cache signupSessionCache *cache.Cache
EmailQueue *email.Queue emailQueue *email.Queue
forgotNkodeCache memcache.ForgotNKodeCache
} }
func NewNKodeAPI(db repository.CustomerUserRepository, queue *email.Queue) NKodeAPI { func NewNKodeAPI(repo repository.CustomerUserRepository, queue *email.Queue) NKodeAPI {
return NKodeAPI{ return NKodeAPI{
Db: db, repo: repo,
EmailQueue: queue, emailQueue: queue,
SignupSessionCache: cache.New(sessionExpiration, sessionCleanupInterval), signupSessionCache: cache.New(sessionExpiration, sessionCleanupInterval),
forgotNkodeCache: memcache.NewForgotNKodeCache(),
} }
} }
func (n *NKodeAPI) CreateNewCustomer(nkodePolicy entities.NKodePolicy, id *entities.CustomerId) (*entities.CustomerId, error) { func (n *NKodeAPI) CreateNewCustomer(nkodePolicy entities.NKodePolicy) (*entities.CustomerID, error) {
newCustomer, err := entities.NewCustomer(nkodePolicy) newCustomer, err := entities.NewCustomer(nkodePolicy)
if id != nil {
newCustomer.Id = *id
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = n.Db.CreateCustomer(*newCustomer) err = n.repo.CreateCustomer(*newCustomer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &newCustomer.Id, nil return &newCustomer.ID, nil
} }
func (n *NKodeAPI) GenerateSignupResetInterface(userEmail entities.UserEmail, customerId entities.CustomerId, kp entities.KeypadDimension, reset bool) (*entities.SignupResetInterface, error) { func (n *NKodeAPI) CreateCustomerWithID(id entities.CustomerID, nkodePolicy entities.NKodePolicy) error {
user, err := n.Db.GetUser(userEmail, customerId) newCustomer, err := entities.NewCustomer(nkodePolicy)
if err != nil {
return err
}
newCustomer.ID = id
if err = n.repo.CreateCustomer(*newCustomer); err != nil {
return err
}
return nil
}
func (n *NKodeAPI) GenerateSignupResetInterface(userEmail entities.UserEmail, customerId entities.CustomerID, kp entities.KeypadDimension, reset bool) (*entities.SignupResetInterface, error) {
user, err := n.repo.GetUser(userEmail, customerId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -58,7 +70,7 @@ func (n *NKodeAPI) GenerateSignupResetInterface(userEmail entities.UserEmail, cu
log.Printf("user %s already exists", string(userEmail)) log.Printf("user %s already exists", string(userEmail))
return nil, config.ErrUserAlreadyExists return nil, config.ErrUserAlreadyExists
} }
svgIdxInterface, err := n.Db.RandomSvgIdxInterface(kp) svgIdxInterface, err := n.repo.RandomSvgIdxInterface(kp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -66,30 +78,29 @@ func (n *NKodeAPI) GenerateSignupResetInterface(userEmail entities.UserEmail, cu
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := n.SignupSessionCache.Add(signupSession.Id.String(), *signupSession, sessionExpiration); err != nil { if err := n.signupSessionCache.Add(signupSession.ID.String(), *signupSession, sessionExpiration); err != nil {
return nil, err return nil, err
} }
svgInterface, err := n.Db.GetSvgStringInterface(signupSession.LoginUserInterface.SvgId) svgInterface, err := n.repo.GetSvgStringInterface(signupSession.LoginUserInterface.SvgID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp := entities.SignupResetInterface{ resp := entities.SignupResetInterface{
UserIdxInterface: signupSession.SetIdxInterface, UserIdxInterface: signupSession.SetIdxInterface,
SvgInterface: svgInterface, SvgInterface: svgInterface,
SessionId: uuid.UUID(signupSession.Id).String(), SessionID: uuid.UUID(signupSession.ID).String(),
Colors: signupSession.Colors, Colors: signupSession.Colors,
} }
return &resp, nil return &resp, nil
} }
func (n *NKodeAPI) SetNKode(customerId entities.CustomerId, sessionId entities.SessionId, keySelection entities.KeySelection) (entities.IdxInterface, error) { func (n *NKodeAPI) SetNKode(customerId entities.CustomerID, sessionId entities.SessionID, keySelection entities.KeySelection) (entities.IdxInterface, error) {
_, err := n.Db.GetCustomer(customerId) _, err := n.repo.GetCustomer(customerId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
session, exists := n.SignupSessionCache.Get(sessionId.String()) session, exists := n.signupSessionCache.Get(sessionId.String())
if !exists { if !exists {
log.Printf("session id does not exist %s", sessionId) log.Printf("session id does not exist %s", sessionId)
return nil, config.ErrSignupSessionDNE return nil, config.ErrSignupSessionDNE
@@ -103,12 +114,12 @@ func (n *NKodeAPI) SetNKode(customerId entities.CustomerId, sessionId entities.S
if err != nil { if err != nil {
return nil, err return nil, err
} }
n.SignupSessionCache.Set(sessionId.String(), userSession, sessionExpiration) n.signupSessionCache.Set(sessionId.String(), userSession, sessionExpiration)
return confirmInterface, nil return confirmInterface, nil
} }
func (n *NKodeAPI) ConfirmNKode(customerId entities.CustomerId, sessionId entities.SessionId, keySelection entities.KeySelection) error { func (n *NKodeAPI) ConfirmNKode(customerId entities.CustomerID, sessionId entities.SessionID, keySelection entities.KeySelection) error {
session, exists := n.SignupSessionCache.Get(sessionId.String()) session, exists := n.signupSessionCache.Get(sessionId.String())
if !exists { if !exists {
log.Printf("session id does not exist %s", sessionId) log.Printf("session id does not exist %s", sessionId)
return config.ErrSignupSessionDNE return config.ErrSignupSessionDNE
@@ -118,7 +129,7 @@ func (n *NKodeAPI) ConfirmNKode(customerId entities.CustomerId, sessionId entiti
// handle the case where the type assertion fails // handle the case where the type assertion fails
return config.ErrSignupSessionDNE return config.ErrSignupSessionDNE
} }
customer, err := n.Db.GetCustomer(customerId) customer, err := n.repo.GetCustomer(customerId)
if err != nil { if err != nil {
return err return err
} }
@@ -134,16 +145,16 @@ func (n *NKodeAPI) ConfirmNKode(customerId entities.CustomerId, sessionId entiti
return err return err
} }
if userSession.Reset { if userSession.Reset {
err = n.Db.UpdateUserNKode(*user) err = n.repo.UpdateUserNKode(*user)
} else { } else {
err = n.Db.WriteNewUser(*user) err = n.repo.WriteNewUser(*user)
} }
n.SignupSessionCache.Delete(userSession.Id.String()) n.signupSessionCache.Delete(userSession.ID.String())
return err return err
} }
func (n *NKodeAPI) GetLoginInterface(userEmail entities.UserEmail, customerId entities.CustomerId) (*entities.LoginInterface, error) { func (n *NKodeAPI) GetLoginInterface(userEmail entities.UserEmail, customerId entities.CustomerID) (*entities.LoginInterface, error) {
user, err := n.Db.GetUser(userEmail, customerId) user, err := n.repo.GetUser(userEmail, customerId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -151,7 +162,7 @@ func (n *NKodeAPI) GetLoginInterface(userEmail entities.UserEmail, customerId en
log.Printf("user %s for customer %s dne", userEmail, customerId) log.Printf("user %s for customer %s dne", userEmail, customerId)
return nil, config.ErrUserForCustomerDNE return nil, config.ErrUserForCustomerDNE
} }
svgInterface, err := n.Db.GetSvgStringInterface(user.Interface.SvgId) svgInterface, err := n.repo.GetSvgStringInterface(user.Interface.SvgID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -165,12 +176,12 @@ func (n *NKodeAPI) GetLoginInterface(userEmail entities.UserEmail, customerId en
return &resp, nil return &resp, nil
} }
func (n *NKodeAPI) Login(customerId entities.CustomerId, userEmail entities.UserEmail, keySelection entities.KeySelection) (*security.AuthenticationTokens, error) { func (n *NKodeAPI) Login(customerId entities.CustomerID, userEmail entities.UserEmail, keySelection entities.KeySelection) (*security.AuthenticationTokens, error) {
customer, err := n.Db.GetCustomer(customerId) customer, err := n.repo.GetCustomer(customerId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
user, err := n.Db.GetUser(userEmail, customerId) user, err := n.repo.GetUser(userEmail, customerId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -184,37 +195,38 @@ func (n *NKodeAPI) Login(customerId entities.CustomerId, userEmail entities.User
} }
if user.Renew { if user.Renew {
err = n.Db.RefreshUserPasscode(*user, passcode, customer.Attributes) err = n.repo.RefreshUserPasscode(*user, passcode, customer.Attributes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
jwtToken, err := security.NewAuthenticationTokens(string(user.Email), uuid.UUID(customerId)) jwtToken, err := security.NewAuthenticationTokens(string(user.Email), uuid.UUID(customerId))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = n.Db.UpdateUserRefreshToken(user.Id, jwtToken.RefreshToken); err != nil { if err = n.repo.UpdateUserRefreshToken(user.ID, jwtToken.RefreshToken); err != nil {
return nil, err return nil, err
} }
if err = user.Interface.LoginShuffle(); err != nil { if err = user.Interface.LoginShuffle(); err != nil {
return nil, err return nil, err
} }
if err = n.Db.UpdateUserInterface(user.Id, user.Interface); err != nil { if err = n.repo.UpdateUserInterface(user.ID, user.Interface); err != nil {
return nil, err return nil, err
} }
return &jwtToken, nil return &jwtToken, nil
} }
func (n *NKodeAPI) RenewAttributes(customerId entities.CustomerId) error { func (n *NKodeAPI) RenewAttributes(customerId entities.CustomerID) error {
return n.Db.Renew(customerId) return n.repo.Renew(customerId)
} }
func (n *NKodeAPI) RandomSvgInterface() ([]string, error) { func (n *NKodeAPI) RandomSvgInterface() ([]string, error) {
return n.Db.RandomSvgInterface(entities.KeypadMax) return n.repo.RandomSvgInterface(entities.KeypadMax)
} }
func (n *NKodeAPI) RefreshToken(userEmail entities.UserEmail, customerId entities.CustomerId, refreshToken string) (string, error) { func (n *NKodeAPI) RefreshToken(userEmail entities.UserEmail, customerId entities.CustomerID, refreshToken string) (string, error) {
user, err := n.Db.GetUser(userEmail, customerId) user, err := n.repo.GetUser(userEmail, customerId)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -236,8 +248,8 @@ func (n *NKodeAPI) RefreshToken(userEmail entities.UserEmail, customerId entitie
return security.EncodeAndSignClaims(newAccessClaims) return security.EncodeAndSignClaims(newAccessClaims)
} }
func (n *NKodeAPI) ResetNKode(userEmail entities.UserEmail, customerId entities.CustomerId) error { func (n *NKodeAPI) ForgotNKode(userEmail entities.UserEmail, customerId entities.CustomerID) error {
user, err := n.Db.GetUser(userEmail, customerId) user, err := n.repo.GetUser(userEmail, customerId)
if err != nil { if err != nil {
return fmt.Errorf("error getting user in rest nkode %v", err) return fmt.Errorf("error getting user in rest nkode %v", err)
} }
@@ -246,7 +258,7 @@ func (n *NKodeAPI) ResetNKode(userEmail entities.UserEmail, customerId entities.
return nil return nil
} }
nkodeResetJwt, err := security.ResetNKodeToken(string(userEmail), uuid.UUID(customerId)) nkodeResetJwt, err := security.ResetNKodeToken(string(userEmail), uuid.UUID(customerId).String())
if err != nil { if err != nil {
return err return err
} }
@@ -261,6 +273,22 @@ func (n *NKodeAPI) ResetNKode(userEmail entities.UserEmail, customerId entities.
Subject: "nKode Reset", Subject: "nKode Reset",
Content: htmlBody, Content: htmlBody,
} }
n.EmailQueue.AddEmail(email) n.emailQueue.AddEmail(email)
n.forgotNkodeCache.Set(userEmail, customerId)
return nil
}
func (n *NKodeAPI) Signout(userEmail entities.UserEmail, customerId entities.CustomerID) error {
user, err := n.repo.GetUser(userEmail, customerId)
if err != nil {
return err
}
if user == nil {
log.Printf("user %s for customer %s dne", userEmail, customerId)
return config.ErrUserForCustomerDNE
}
if err = n.repo.UpdateUserRefreshToken(user.ID, ""); err != nil {
return err
}
return nil return nil
} }

View File

@@ -6,7 +6,6 @@ import (
"git.infra.nkode.tech/dkelly/nkode-core/entities" "git.infra.nkode.tech/dkelly/nkode-core/entities"
"git.infra.nkode.tech/dkelly/nkode-core/repository" "git.infra.nkode.tech/dkelly/nkode-core/repository"
"git.infra.nkode.tech/dkelly/nkode-core/security" "git.infra.nkode.tech/dkelly/nkode-core/security"
"git.infra.nkode.tech/dkelly/nkode-core/sqlc"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"log" "log"
"os" "os"
@@ -19,26 +18,18 @@ func TestNKodeAPI(t *testing.T) {
dbPath := os.Getenv("TEST_DB") dbPath := os.Getenv("TEST_DB")
ctx := context.Background() ctx := context.Background()
sqliteDb, err := sqlc.OpenSqliteDb(dbPath) sqlitedb, err := repository.NewSqliteNKodeRepo(ctx, dbPath)
assert.NoError(t, err) if err != nil {
queue, err := sqlc.NewQueue(sqliteDb, ctx)
assert.NoError(t, err)
queue.Start()
defer func(queue *sqlc.Queue) {
if err := queue.Stop(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
}(queue) sqlitedb.Start()
sqlitedb := repository.NewSqliteRepository(queue, ctx) defer func(sqldb *repository.SqliteNKodeRepo) {
testNKodeAPI(t, &sqlitedb) if err := sqldb.Stop(); err != nil {
log.Fatal(err)
}
}(sqlitedb)
testNKodeAPI(t, sqlitedb)
//if _, err := os.Stat(dbPath); err == nil {
// err = os.Remove(dbPath)
// assert.NoError(t, err)
//} else {
// assert.NoError(t, err)
//}
} }
func testNKodeAPI(t *testing.T, db repository.CustomerUserRepository) { func testNKodeAPI(t *testing.T, db repository.CustomerUserRepository) {
@@ -51,18 +42,18 @@ func testNKodeAPI(t *testing.T, db repository.CustomerUserRepository) {
attrsPerKey := 5 attrsPerKey := 5
numbOfKeys := 4 numbOfKeys := 4
for idx := 0; idx < 1; idx++ { for idx := 0; idx < 1; idx++ {
userEmail := entities.UserEmail("test_username" + security.GenerateRandomString(12) + "@example.com") userEmail := entities.UserEmail("test_username" + security.GenerateNonSecureRandomString(12) + "@example.com")
passcodeLen := 4 passcodeLen := 4
nkodePolicy := entities.NewDefaultNKodePolicy() nkodePolicy := entities.NewDefaultNKodePolicy()
keypadSize := entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} keypadSize := entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
nkodeApi := NewNKodeAPI(db, queue) nkodeApi := NewNKodeAPI(db, queue)
customerId, err := nkodeApi.CreateNewCustomer(nkodePolicy, nil) customerId, err := nkodeApi.CreateNewCustomer(nkodePolicy)
assert.NoError(t, err) assert.NoError(t, err)
signupResponse, err := nkodeApi.GenerateSignupResetInterface(userEmail, *customerId, keypadSize, false) signupResponse, err := nkodeApi.GenerateSignupResetInterface(userEmail, *customerId, keypadSize, false)
assert.NoError(t, err) assert.NoError(t, err)
setInterface := signupResponse.UserIdxInterface setInterface := signupResponse.UserIdxInterface
sessionIdStr := signupResponse.SessionId sessionIdStr := signupResponse.SessionID
sessionId, err := entities.SessionIdFromString(sessionIdStr) sessionId, err := entities.SessionIDFromString(sessionIdStr)
assert.NoError(t, err) assert.NoError(t, err)
keypadSize = entities.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys} keypadSize = entities.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys}
userPasscode := setInterface[:passcodeLen] userPasscode := setInterface[:passcodeLen]
@@ -98,8 +89,8 @@ func testNKodeAPI(t *testing.T, db repository.CustomerUserRepository) {
resetResponse, err := nkodeApi.GenerateSignupResetInterface(userEmail, *customerId, keypadSize, true) resetResponse, err := nkodeApi.GenerateSignupResetInterface(userEmail, *customerId, keypadSize, true)
assert.NoError(t, err) assert.NoError(t, err)
setInterface = resetResponse.UserIdxInterface setInterface = resetResponse.UserIdxInterface
sessionIdStr = resetResponse.SessionId sessionIdStr = resetResponse.SessionID
sessionId, err = entities.SessionIdFromString(sessionIdStr) sessionId, err = entities.SessionIDFromString(sessionIdStr)
assert.NoError(t, err) assert.NoError(t, err)
keypadSize = entities.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys} keypadSize = entities.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys}
userPasscode = setInterface[:passcodeLen] userPasscode = setInterface[:passcodeLen]

View File

@@ -1,258 +1,86 @@
package main package main
import ( import (
"context"
"database/sql" "database/sql"
"encoding/json" _ "embed"
"flag"
"fmt" "fmt"
_ "github.com/mattn/go-sqlite3" // Import the SQLite3 driver "git.infra.nkode.tech/dkelly/nkode-core/repository"
"io/ioutil" "git.infra.nkode.tech/dkelly/nkode-core/sqlite"
_ "github.com/mattn/go-sqlite3"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
type Icon struct {
Body string `json:"body"`
Width *int `json:"width,omitempty"`
}
// Root Define the Root struct to represent the entire JSON structure
type Root struct {
Prefix string `json:"prefix"`
Icons map[string]Icon `json:"icons"`
Height int `json:"height"`
}
func main() { func main() {
testDbPath := os.Getenv("TEST_DB_PATH") sqliteSchema, err := sqlite.FS.ReadFile("schema.sql")
dbPath := os.Getenv("DB_PATH")
dbPaths := []string{testDbPath, dbPath}
flaticonSvgDir := os.Getenv("SVG_DIR")
//dbPath := "/Users/donov/Desktop/nkode.db"
//dbPaths := []string{dbPath}
//outputStr := MakeSvgFiles()
for _, path := range dbPaths {
MakeTables(path)
FlaticonToSqlite(path, flaticonSvgDir)
//SvgToSqlite(path, outputStr)
}
}
func FlaticonToSqlite(dbPath string, svgDir string) {
db, err := sql.Open("sqlite3", dbPath)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer db.Close() commandLine := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
dbPath := commandLine.String("db-path", "", "Path to the database")
svgPath := commandLine.String("svg-path", "", "Path to the SVG directory")
if err = commandLine.Parse(os.Args[1:]); err != nil {
log.Fatalf("Failed to parse flags: %v", err)
}
if err = MakeTables(*dbPath, string(sqliteSchema)); err != nil {
log.Fatal(err)
}
ctx := context.Background()
sqliteRepo, err := repository.NewSqliteNKodeRepo(ctx, *dbPath)
sqliteRepo.Start()
defer func(sqliteRepo *repository.SqliteNKodeRepo) {
if err := sqliteRepo.Stop(); err != nil {
log.Fatal(err)
}
}(sqliteRepo)
// Open the directory FlaticonToSqlite(sqliteRepo, *svgPath)
log.Println(fmt.Sprintf("Successfully added all SVGs in %s to the database at %s\n", *svgPath, *dbPath))
}
func FlaticonToSqlite(repo *repository.SqliteNKodeRepo, svgDir string) {
files, err := os.ReadDir(svgDir) files, err := os.ReadDir(svgDir)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
for _, file := range files { for _, file := range files {
// Check if it is a regular file (not a directory) and has a .svg extension
if file.IsDir() || filepath.Ext(file.Name()) != ".svg" { if file.IsDir() || filepath.Ext(file.Name()) != ".svg" {
continue continue
} }
filePath := filepath.Join(svgDir, file.Name()) filePath := filepath.Join(svgDir, file.Name())
// Read the file contents
content, err := os.ReadFile(filePath) content, err := os.ReadFile(filePath)
if err != nil { if err != nil {
log.Println("Error reading file:", filePath, err) log.Println("Error reading file:", filePath, err)
continue continue
} }
// Print the file name and first few bytes of the file content if err = repo.AddSvg(string(content)); err != nil {
insertSql := `
INSERT INTO svg_icon (svg)
VALUES (?)
`
_, err = db.Exec(insertSql, string(content))
if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
} }
func SvgToSqlite(dbPath string, outputStr string) { func MakeTables(dbPath string, schema string) error {
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
if err = os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
return err
}
if _, err = os.Create(dbPath); err != nil {
return err
}
}
db, err := sql.Open("sqlite3", dbPath) db, err := sql.Open("sqlite3", dbPath)
if err != nil { if err != nil {
log.Fatal(err) return err
} }
defer db.Close() if _, err = db.Exec(schema); err != nil {
return err
lines := strings.Split(outputStr, "\n")
insertSql := `
INSERT INTO svg_icon (svg)
VALUES (?)
`
for _, line := range lines {
if line == "" {
continue
}
_, err := db.Exec(insertSql, line)
if err != nil {
log.Fatal(err)
}
}
}
func MakeSvgFiles() string {
jsonFiles, err := GetAllFiles("./core/sqlite-init/json")
if err != nil {
log.Fatalf("Error getting JSON files: %v", err)
}
if len(jsonFiles) == 0 {
log.Fatal("No JSON files found in ./json folder")
}
var outputStr string
strSet := make(map[string]struct{})
for _, filename := range jsonFiles {
fileData, err := LoadJson(filename)
if err != nil {
log.Print("Error loading JSON file: ", err)
continue
}
height := fileData.Height
for name, icon := range fileData.Icons {
width := height
parts := strings.Split(name, "-")
if len(parts) <= 0 {
log.Print(name, " has no parts")
continue
}
part0 := parts[0]
_, exists := strSet[part0]
if exists {
continue
}
if icon.Width != nil {
width = *icon.Width
}
strSet[part0] = struct{}{}
if icon.Body == "" {
continue
}
outputStr = fmt.Sprintf("%s<svg viewBox=\"0 0 %d %d\" xmlns=\"http://www.w3.org/2000/svg\">%s</svg>\n", outputStr, width, height, icon.Body)
}
}
return outputStr
}
func GetAllFiles(dir string) ([]string, error) {
// Use ioutil.ReadDir to list all files in the directory
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("unable to read directory: %v", err)
}
// Create a slice to hold the JSON filenames
var jsonFiles []string
// Loop through the files and filter out JSON files
for _, file := range files {
if !file.IsDir() && filepath.Ext(file.Name()) == ".json" {
jsonFiles = append(jsonFiles, filepath.Join(dir, file.Name()))
}
}
return jsonFiles, nil
}
func LoadJson(filename string) (*Root, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read file %s: %v", filename, err)
}
var root Root
err = json.Unmarshal(data, &root)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
}
return &root, nil
}
func MakeTables(dbPath string) {
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
log.Fatal(err)
}
defer db.Close()
createTable := `
PRAGMA journal_mode=WAL;
--PRAGMA busy_timeout = 5000; -- Wait up to 5 seconds
--PRAGMA synchronous = NORMAL; -- Reduce sync frequency for less locking
--PRAGMA cache_size = -16000; -- Increase cache size (16MB)PRAGMA
CREATE TABLE IF NOT EXISTS customer (
id TEXT NOT NULL PRIMARY KEY
,max_nkode_len INTEGER NOT NULL
,min_nkode_len INTEGER NOT NULL
,distinct_sets INTEGER NOT NULL
,distinct_attributes INTEGER NOT NULL
,lock_out INTEGER NOT NULL
,expiration INTEGER NOT NULL
,attribute_values BLOB NOT NULL
,set_values BLOB NOT NULL
,last_renew TEXT NOT NULL
,created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS user (
id TEXT NOT NULL PRIMARY KEY
,email TEXT NOT NULL
-- first_name TEXT NOT NULL
-- last_name TEXT NOT NULL
,renew INT NOT NULL
,refresh_token TEXT
,customer_id TEXT NOT NULL
-- Enciphered Passcode
,code TEXT NOT NULL
,mask TEXT NOT NULL
-- Keypad Dimensions
,attributes_per_key INT NOT NULL
,number_of_keys INT NOT NULL
-- User Keys
,alpha_key BLOB NOT NULL
,set_key BLOB NOT NULL
,pass_key BLOB NOT NULL
,mask_key BLOB NOT NULL
,salt BLOB NOT NULL
,max_nkode_len INT NOT NULL
-- User Interface
,idx_interface BLOB NOT NULL
,svg_id_interface BLOB NOT NULL
,last_login TEXT NULL
,created_at TEXT
,FOREIGN KEY (customer_id) REFERENCES customer(id)
,UNIQUE(customer_id, email)
);
CREATE TABLE IF NOT EXISTS svg_icon (
id INTEGER PRIMARY KEY AUTOINCREMENT
,svg TEXT NOT NULL
);
`
_, err = db.Exec(createTable)
if err != nil {
log.Fatal(err)
} }
return db.Close()
} }

View File

@@ -14,6 +14,13 @@ import (
"time" "time"
) )
const (
EmailRetryExpiration = 5 * time.Minute
SesCleanupInterval = 10 * time.Minute
EmailQueueBufferSize = 100
MaxEmailsPerSecond = 13
)
type Client interface { type Client interface {
SendEmail(Email) error SendEmail(Email) error
} }
@@ -36,14 +43,9 @@ type SESClient struct {
ResetCache *cache.Cache ResetCache *cache.Cache
} }
const (
emailRetryExpiration = 5 * time.Minute
sesCleanupInterval = 10 * time.Minute
)
func NewSESClient() SESClient { func NewSESClient() SESClient {
return SESClient{ return SESClient{
ResetCache: cache.New(emailRetryExpiration, sesCleanupInterval), ResetCache: cache.New(EmailRetryExpiration, SesCleanupInterval),
} }
} }
@@ -79,7 +81,7 @@ func (s *SESClient) SendEmail(email Email) error {
Source: aws.String(email.Sender), Source: aws.String(email.Sender),
} }
if err = s.ResetCache.Add(email.Recipient, nil, emailRetryExpiration); err != nil { if err = s.ResetCache.Add(email.Recipient, nil, EmailRetryExpiration); err != nil {
return err return err
} }

View File

@@ -10,7 +10,7 @@ import (
) )
type Customer struct { type Customer struct {
Id CustomerId ID CustomerID
NKodePolicy NKodePolicy NKodePolicy NKodePolicy
Attributes CustomerAttributes Attributes CustomerAttributes
} }
@@ -21,7 +21,7 @@ func NewCustomer(nkodePolicy NKodePolicy) (*Customer, error) {
return nil, err return nil, err
} }
customer := Customer{ customer := Customer{
Id: CustomerId(uuid.New()), ID: CustomerID(uuid.New()),
NKodePolicy: nkodePolicy, NKodePolicy: nkodePolicy,
Attributes: *customerAttrs, Attributes: *customerAttrs,
} }
@@ -87,7 +87,7 @@ func (c *Customer) RenewKeys() ([]uint64, []uint64, error) {
func (c *Customer) ToSqlcCreateCustomerParams() sqlc.CreateCustomerParams { func (c *Customer) ToSqlcCreateCustomerParams() sqlc.CreateCustomerParams {
return sqlc.CreateCustomerParams{ return sqlc.CreateCustomerParams{
ID: uuid.UUID(c.Id).String(), ID: uuid.UUID(c.ID).String(),
MaxNkodeLen: int64(c.NKodePolicy.MaxNkodeLen), MaxNkodeLen: int64(c.NKodePolicy.MaxNkodeLen),
MinNkodeLen: int64(c.NKodePolicy.MinNkodeLen), MinNkodeLen: int64(c.NKodePolicy.MinNkodeLen),
DistinctSets: int64(c.NKodePolicy.DistinctSets), DistinctSets: int64(c.NKodePolicy.DistinctSets),

View File

@@ -21,7 +21,7 @@ func testCustomerValidKeyEntry(t *testing.T) {
nkodePolicy := NewDefaultNKodePolicy() nkodePolicy := NewDefaultNKodePolicy()
customer, err := NewCustomer(nkodePolicy) customer, err := NewCustomer(nkodePolicy)
assert.NoError(t, err) assert.NoError(t, err)
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(SvgIDInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
userEmail := "testing@example.com" userEmail := "testing@example.com"
@@ -45,7 +45,7 @@ func testCustomerIsValidNKode(t *testing.T) {
nkodePolicy := NewDefaultNKodePolicy() nkodePolicy := NewDefaultNKodePolicy()
customer, err := NewCustomer(nkodePolicy) customer, err := NewCustomer(nkodePolicy)
assert.NoError(t, err) assert.NoError(t, err)
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(SvgIDInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
userEmail := "testing123@example.com" userEmail := "testing123@example.com"

View File

@@ -9,25 +9,30 @@ import (
type KeySelection []int type KeySelection []int
type CustomerId uuid.UUID type CustomerID uuid.UUID
func CustomerIdToString(customerId CustomerId) string { func (c *CustomerID) String() string {
customerUuid := uuid.UUID(customerId) id := uuid.UUID(*c)
return customerUuid.String() return id.String()
} }
type SessionId uuid.UUID type SessionID uuid.UUID
type UserId uuid.UUID type UserID uuid.UUID
func UserIdFromString(userId string) UserId { func (u *UserID) String() string {
id, err := uuid.Parse(userId) id := uuid.UUID(*u)
return id.String()
}
func UserIDFromString(userID string) UserID {
id, err := uuid.Parse(userID)
if err != nil { if err != nil {
fmt.Errorf("unable to parse user id %+v", err) fmt.Errorf("unable to parse user id %+v", err)
} }
return UserId(id) return UserID(id)
} }
func (s *SessionId) String() string { func (s *SessionID) String() string {
id := uuid.UUID(*s) id := uuid.UUID(*s)
return id.String() return id.String()
} }
@@ -44,15 +49,15 @@ func ParseEmail(email string) (UserEmail, error) {
} }
type IdxInterface []int type IdxInterface []int
type SvgIdInterface []int type SvgIDInterface []int
func SessionIdFromString(sessionId string) (SessionId, error) { func SessionIDFromString(sessionID string) (SessionID, error) {
id, err := uuid.Parse(sessionId) id, err := uuid.Parse(sessionID)
if err != nil { if err != nil {
return SessionId{}, err return SessionID{}, err
} }
return SessionId(id), nil return SessionID(id), nil
} }
type EncipheredNKode struct { type EncipheredNKode struct {
@@ -86,7 +91,7 @@ var SetColors = []RGBColor{
} }
type SignupResetInterface struct { type SignupResetInterface struct {
SessionId string `json:"session_id"` SessionID string `json:"session_id"`
UserIdxInterface IdxInterface `json:"user_interface"` UserIdxInterface IdxInterface `json:"user_interface"`
SvgInterface []string `json:"svg_interface"` SvgInterface []string `json:"svg_interface"`
Colors []RGBColor `json:"colors"` Colors []RGBColor `json:"colors"`
@@ -99,3 +104,14 @@ type LoginInterface struct {
NumbOfKeys int `json:"numb_of_keys"` NumbOfKeys int `json:"numb_of_keys"`
Colors []RGBColor `json:"colors"` Colors []RGBColor `json:"colors"`
} }
type UserPermission int
const (
Default UserPermission = iota
Admin
)
func (p UserPermission) String() string {
return [...]string{"Default", "Admin"}[p]
}

View File

@@ -3,12 +3,12 @@ package entities
import "git.infra.nkode.tech/dkelly/nkode-core/config" import "git.infra.nkode.tech/dkelly/nkode-core/config"
type NKodePolicy struct { type NKodePolicy struct {
MaxNkodeLen int `json:"max_nkode_len"` MaxNkodeLen int `form:"max_nkode_len"`
MinNkodeLen int `json:"min_nkode_len"` MinNkodeLen int `form:"min_nkode_len"`
DistinctSets int `json:"distinct_sets"` DistinctSets int `form:"distinct_sets"`
DistinctAttributes int `json:"distinct_attributes"` DistinctAttributes int `form:"distinct_attributes"`
LockOut int `json:"lock_out"` LockOut int `form:"lock_out"`
Expiration int `json:"expiration"` // seconds, -1 no expiration Expiration int `form:"expiration"` // seconds, -1 no expiration
} }
func NewDefaultNKodePolicy() NKodePolicy { func NewDefaultNKodePolicy() NKodePolicy {

View File

@@ -8,8 +8,8 @@ import (
) )
type User struct { type User struct {
Id UserId ID UserID
CustomerId CustomerId CustomerID CustomerID
Email UserEmail Email UserEmail
EncipheredPasscode EncipheredNKode EncipheredPasscode EncipheredNKode
Kp KeypadDimension Kp KeypadDimension
@@ -130,13 +130,13 @@ func NewUser(customer Customer, userEmail string, passcodeIdx []int, ui UserInte
return nil, err return nil, err
} }
newUser := User{ newUser := User{
Id: UserId(uuid.New()), ID: UserID(uuid.New()),
Email: UserEmail(userEmail), Email: UserEmail(userEmail),
EncipheredPasscode: *encipheredNKode, EncipheredPasscode: *encipheredNKode,
CipherKeys: *newKeys, CipherKeys: *newKeys,
Interface: ui, Interface: ui,
Kp: kp, Kp: kp,
CustomerId: customer.Id, CustomerID: customer.ID,
} }
return &newUser, nil return &newUser, nil
} }

View File

@@ -9,15 +9,15 @@ import (
type UserInterface struct { type UserInterface struct {
IdxInterface IdxInterface IdxInterface IdxInterface
SvgId SvgIdInterface SvgID SvgIDInterface
Kp *KeypadDimension Kp *KeypadDimension
} }
func NewUserInterface(kp *KeypadDimension, svgId SvgIdInterface) (*UserInterface, error) { func NewUserInterface(kp *KeypadDimension, svgID SvgIDInterface) (*UserInterface, error) {
idxInterface := security.IdentityArray(kp.TotalAttrs()) idxInterface := security.IdentityArray(kp.TotalAttrs())
userInterface := UserInterface{ userInterface := UserInterface{
IdxInterface: idxInterface, IdxInterface: idxInterface,
SvgId: svgId, SvgID: svgID,
Kp: kp, Kp: kp,
} }
if err := userInterface.RandomShuffle(); err != nil { if err := userInterface.RandomShuffle(); err != nil {

View File

@@ -11,8 +11,8 @@ import (
) )
type UserSignSession struct { type UserSignSession struct {
Id SessionId ID SessionID
CustomerId CustomerId CustomerID CustomerID
LoginUserInterface UserInterface LoginUserInterface UserInterface
Kp KeypadDimension Kp KeypadDimension
SetIdxInterface IdxInterface SetIdxInterface IdxInterface
@@ -24,7 +24,7 @@ type UserSignSession struct {
Colors []RGBColor Colors []RGBColor
} }
func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId CustomerId, svgInterface SvgIdInterface, reset bool) (*UserSignSession, error) { func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId CustomerID, svgInterface SvgIDInterface, reset bool) (*UserSignSession, error) {
loginInterface, err := NewUserInterface(&kp, svgInterface) loginInterface, err := NewUserInterface(&kp, svgInterface)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -34,8 +34,8 @@ func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId C
return nil, err return nil, err
} }
session := UserSignSession{ session := UserSignSession{
Id: SessionId(uuid.New()), ID: SessionID(uuid.New()),
CustomerId: customerId, CustomerID: customerId,
LoginUserInterface: *loginInterface, LoginUserInterface: *loginInterface,
SetIdxInterface: signupInterface.IdxInterface, SetIdxInterface: signupInterface.IdxInterface,
ConfirmIdxInterface: nil, ConfirmIdxInterface: nil,

View File

@@ -64,7 +64,7 @@ func TestUserInterface_RandomShuffle(t *testing.T) {
AttrsPerKey: 10, AttrsPerKey: 10,
NumbOfKeys: 8, NumbOfKeys: 8,
} }
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(SvgIDInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
userInterfaceCopy := make([]int, len(userInterface.IdxInterface)) userInterfaceCopy := make([]int, len(userInterface.IdxInterface))
@@ -87,7 +87,7 @@ func TestUserInterface_DisperseInterface(t *testing.T) {
for idx := 0; idx < 10000; idx++ { for idx := 0; idx < 10000; idx++ {
kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10} kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10}
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(SvgIDInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
preDispersion, err := userInterface.AttributeAdjacencyGraph() preDispersion, err := userInterface.AttributeAdjacencyGraph()
@@ -106,7 +106,7 @@ func TestUserInterface_DisperseInterface(t *testing.T) {
func TestUserInterface_PartialInterfaceShuffle(t *testing.T) { func TestUserInterface_PartialInterfaceShuffle(t *testing.T) {
kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10} kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10}
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(SvgIDInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
preShuffle := userInterface.IdxInterface preShuffle := userInterface.IdxInterface
@@ -123,11 +123,4 @@ func TestUserInterface_PartialInterfaceShuffle(t *testing.T) {
return n == true return n == true
}) })
assert.False(t, allTrue) assert.False(t, allTrue)
allFalse := all.All[bool](shuffleCompare, func(n bool) bool {
return n == false
})
assert.False(t, allFalse)
} }

25
go.mod
View File

@@ -7,6 +7,7 @@ require (
github.com/aws/aws-sdk-go-v2 v1.33.0 github.com/aws/aws-sdk-go-v2 v1.33.0
github.com/aws/aws-sdk-go-v2/config v1.29.1 github.com/aws/aws-sdk-go-v2/config v1.29.1
github.com/aws/aws-sdk-go-v2/service/ses v1.29.6 github.com/aws/aws-sdk-go-v2/service/ses v1.29.6
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/mattn/go-sqlite3 v1.14.24 github.com/mattn/go-sqlite3 v1.14.24
@@ -27,8 +28,32 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 // indirect
github.com/aws/smithy-go v1.22.1 // indirect github.com/aws/smithy-go v1.22.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

76
go.sum
View File

@@ -28,31 +28,107 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 h1:BRVDbewN6VZcwr+FBOszDKvYeXY1
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9/go.mod h1:f6vjfZER1M17Fokn0IzssOTMT2N8ZSq+7jnNF0tArvw= github.com/aws/aws-sdk-go-v2/service/sts v1.33.9/go.mod h1:f6vjfZER1M17Fokn0IzssOTMT2N8ZSq+7jnNF0tArvw=
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 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/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

401
handler/handler.go Normal file
View File

@@ -0,0 +1,401 @@
package handler
import (
"errors"
"git.infra.nkode.tech/dkelly/nkode-core/api"
"git.infra.nkode.tech/dkelly/nkode-core/config"
"git.infra.nkode.tech/dkelly/nkode-core/entities"
"git.infra.nkode.tech/dkelly/nkode-core/models"
"git.infra.nkode.tech/dkelly/nkode-core/security"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"log"
"strings"
)
type NkodeHandler struct {
API api.NKodeAPI
Logger *log.Logger
}
const (
malformedCustomerID = "malformed customer id"
malformedUserEmail = "malformed user email"
malformedSessionID = "malformed session id"
invalidKeypadDimensions = "invalid keypad dimensions"
)
func (h *NkodeHandler) RegisterRoutes(r *gin.Engine) {
r.Group("/v1/nkode")
{
// r.POST("/create-new-customer", h.CreateNewCustomerHandler)
r.POST("/signup", h.SignupHandler)
r.POST("/set-nkode", h.SetNKodeHandler)
r.POST("/confirm-nkode", h.ConfirmNKodeHandler)
r.POST("/get-login-interface", h.GetLoginInterfaceHandler)
r.POST("/login", h.LoginHandler)
r.POST("/renew-attributes", h.RenewAttributesHandler)
r.POST("/random-svg-interface", h.RandomSvgInterfaceHandler)
r.POST("/refresh-token", h.RefreshTokenHandler)
r.POST("/forgot-nkode", h.ForgotNKodeHandler)
r.POST("/signout", h.SignoutHandler)
r.POST("/reset-nkode", h.ResetHandler)
}
}
func (h *NkodeHandler) CreateNewCustomerHandler(c *gin.Context) {
h.Logger.Println("create new customer")
var newCustomerPost entities.NKodePolicy
if err := c.ShouldBind(&newCustomerPost); err != nil {
handleError(c, err)
return
}
customerId, err := h.API.CreateNewCustomer(newCustomerPost)
if err != nil {
c.String(500, "Internal Server Error")
return
}
h.Logger.Println("create new customer")
c.JSON(200, gin.H{"customer_id": customerId.String()})
}
func (h *NkodeHandler) SignupHandler(c *gin.Context) {
h.Logger.Println("generate signup reset interface")
var postBody models.SignupPostBody
if err := c.ShouldBind(&postBody); err != nil {
handleError(c, err)
return
}
kp := entities.KeypadDimension{
AttrsPerKey: postBody.AttrsPerKey,
NumbOfKeys: postBody.NumbOfKeys,
}
if err := kp.IsValidKeypadDimension(); err != nil {
c.String(400, invalidKeypadDimensions)
return
}
customerId, err := uuid.Parse(postBody.CustomerID)
if err != nil {
c.String(400, malformedCustomerID)
return
}
userEmail, err := entities.ParseEmail(postBody.UserEmail)
if err != nil {
c.String(400, malformedUserEmail)
return
}
resp, err := h.API.GenerateSignupResetInterface(userEmail, entities.CustomerID(customerId), kp, false)
if err != nil {
handleError(c, err)
return
}
c.JSON(200, resp)
}
func (h *NkodeHandler) SetNKodeHandler(c *gin.Context) {
h.Logger.Println("set nkode")
var postBody models.SetNKodePost
if err := c.ShouldBindJSON(&postBody); err != nil {
handleError(c, err)
return
}
customerId, err := uuid.Parse(postBody.CustomerID)
if err != nil {
c.String(400, malformedCustomerID)
return
}
sessionId, err := uuid.Parse(postBody.SessionID)
if err != nil {
c.String(400, malformedSessionID)
return
}
confirmInterface, err := h.API.SetNKode(entities.CustomerID(customerId), entities.SessionID(sessionId), postBody.KeySelection)
if err != nil {
handleError(c, err)
return
}
resp := models.SetNKodeResp{UserInterface: confirmInterface}
c.JSON(200, resp)
}
func (h *NkodeHandler) ConfirmNKodeHandler(c *gin.Context) {
h.Logger.Println("confirm nkode")
var postBody models.ConfirmNKodePost
if err := c.ShouldBindJSON(&postBody); err != nil {
handleError(c, err)
return
}
customerId, err := uuid.Parse(postBody.CustomerID)
if err != nil {
c.String(400, malformedCustomerID)
return
}
sessionId, err := uuid.Parse(postBody.SessionID)
if err != nil {
c.String(400, malformedSessionID)
return
}
if err := h.API.ConfirmNKode(entities.CustomerID(customerId), entities.SessionID(sessionId), postBody.KeySelection); err != nil {
handleError(c, err)
return
}
c.Status(200)
}
func (h *NkodeHandler) GetLoginInterfaceHandler(c *gin.Context) {
h.Logger.Println("get login interface")
var loginInterfacePost models.LoginInterfacePost
if err := c.ShouldBind(&loginInterfacePost); err != nil {
handleError(c, err)
return
}
customerId, err := uuid.Parse(loginInterfacePost.CustomerID)
if err != nil {
c.String(400, malformedCustomerID)
return
}
userEmail, err := entities.ParseEmail(loginInterfacePost.UserEmail)
if err != nil {
c.String(400, malformedUserEmail)
return
}
postBody, err := h.API.GetLoginInterface(userEmail, entities.CustomerID(customerId))
if err != nil {
handleError(c, err)
return
}
c.JSON(200, postBody)
}
func (h *NkodeHandler) LoginHandler(c *gin.Context) {
h.Logger.Println("login")
var loginPost models.LoginPost
if err := c.ShouldBindJSON(&loginPost); err != nil {
handleError(c, err)
return
}
customerId, err := uuid.Parse(loginPost.CustomerID)
if err != nil {
c.String(400, malformedCustomerID)
return
}
userEmail, err := entities.ParseEmail(loginPost.UserEmail)
if err != nil {
c.String(400, malformedUserEmail)
return
}
jwtToken, err := h.API.Login(entities.CustomerID(customerId), userEmail, loginPost.KeySelection)
if err != nil {
handleError(c, err)
return
}
c.JSON(200, jwtToken)
}
func (h *NkodeHandler) RenewAttributesHandler(c *gin.Context) {
h.Logger.Println("renew attributes")
var renewAttributesPost models.RenewAttributesPost
if err := c.ShouldBind(&renewAttributesPost); err != nil {
handleError(c, err)
return
}
customerId, err := uuid.Parse(renewAttributesPost.CustomerID)
if err != nil {
c.String(400, malformedCustomerID)
return
}
if err = h.API.RenewAttributes(entities.CustomerID(customerId)); err != nil {
handleError(c, err)
return
}
c.Status(200)
}
func (h *NkodeHandler) RandomSvgInterfaceHandler(c *gin.Context) {
h.Logger.Println("random svg interface")
svgs, err := h.API.RandomSvgInterface()
if err != nil {
handleError(c, err)
return
}
resp := models.RandomSvgInterfaceResp{Svgs: svgs}
c.JSON(200, resp)
}
func (h *NkodeHandler) RefreshTokenHandler(c *gin.Context) {
h.Logger.Println("refresh token")
refreshToken, err := getBearerToken(c)
if err != nil {
c.String(403, "forbidden")
return
}
refreshClaims, err := security.ParseRegisteredClaimToken(refreshToken)
if err != nil {
c.String(500, "Internal Error")
return
}
customerId, err := uuid.Parse(refreshClaims.Issuer)
if err != nil {
c.String(400, malformedCustomerID)
return
}
userEmail, err := entities.ParseEmail(refreshClaims.Subject)
if err != nil {
c.String(400, malformedUserEmail)
return
}
accessToken, err := h.API.RefreshToken(userEmail, entities.CustomerID(customerId), refreshToken)
if err != nil {
handleError(c, err)
return
}
c.JSON(200, gin.H{"access_token": accessToken})
}
func (h *NkodeHandler) ForgotNKodeHandler(c *gin.Context) {
h.Logger.Println("forgot nkode")
var forgotNKodePost models.ForgotNKodePost
if err := c.ShouldBind(&forgotNKodePost); err != nil {
handleError(c, err)
return
}
customerId, err := uuid.Parse(forgotNKodePost.CustomerID)
if err != nil {
c.String(400, malformedCustomerID)
return
}
userEmail, err := entities.ParseEmail(forgotNKodePost.UserEmail)
if err != nil {
c.String(400, malformedUserEmail)
return
}
if err := h.API.ForgotNKode(userEmail, entities.CustomerID(customerId)); err != nil {
handleError(c, err)
return
}
c.Status(200)
}
func (h *NkodeHandler) SignoutHandler(c *gin.Context) {
h.Logger.Println("signout")
token, err := getBearerToken(c)
if err != nil {
c.String(403, "forbidden")
return
}
accessClaims, err := security.ParseRegisteredClaimToken(token)
if err != nil {
handleError(c, err)
return
}
customerId, err := uuid.Parse(accessClaims.Issuer)
if err != nil {
c.String(400, malformedCustomerID)
return
}
userEmail, err := entities.ParseEmail(accessClaims.Subject)
if err != nil {
c.String(400, malformedUserEmail)
return
}
if err = h.API.Signout(userEmail, entities.CustomerID(customerId)); err != nil {
handleError(c, err)
return
}
c.Status(200)
}
func (h *NkodeHandler) ResetHandler(c *gin.Context) {
h.Logger.Println("reset")
token, err := getBearerToken(c)
if err != nil {
c.String(403, "forbidden")
return
}
resetClaims, err := security.ParseRestNKodeToken(token)
if err != nil {
handleError(c, err)
return
}
var postBody models.SignupPostBody
if err = c.ShouldBind(&postBody); err != nil {
handleError(c, err)
return
}
customerId, err := uuid.Parse(postBody.CustomerID)
if err != nil {
c.String(400, malformedCustomerID)
return
}
userEmail, err := entities.ParseEmail(postBody.UserEmail)
if err != nil {
c.String(400, malformedUserEmail)
return
}
if postBody.UserEmail != resetClaims.Subject ||
postBody.CustomerID != resetClaims.Issuer {
c.String(403, "forbidden")
return
}
kp := entities.KeypadDimension{
AttrsPerKey: postBody.AttrsPerKey,
NumbOfKeys: postBody.NumbOfKeys,
}
if err := kp.IsValidKeypadDimension(); err != nil {
c.String(400, invalidKeypadDimensions)
return
}
resp, err := h.API.GenerateSignupResetInterface(userEmail, entities.CustomerID(customerId), kp, true)
if err != nil {
handleError(c, err)
return
}
c.JSON(200, resp)
}
func handleError(c *gin.Context, err error) {
log.Print("handling error: ", err)
statusCode, _ := config.HttpErrMap[err]
switch statusCode {
case 400:
c.String(400, err.Error())
case 403:
c.String(403, err.Error())
case 500:
c.String(500, "Internal Server Error")
default:
log.Print("unknown error: ", err)
c.String(500, "Internal Server Error")
}
}
func getBearerToken(c *gin.Context) (string, error) {
authHeader := c.GetHeader("Authorization")
// Check if the Authorization header is present and starts with "Bearer "
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
return "", errors.New("forbidden")
}
token := strings.TrimPrefix(authHeader, "Bearer ")
return token, nil
}

364
handler/handler_test.go Normal file
View File

@@ -0,0 +1,364 @@
package handler
import (
"bytes"
"context"
"encoding/json"
"fmt"
"git.infra.nkode.tech/dkelly/nkode-core/api"
"git.infra.nkode.tech/dkelly/nkode-core/email"
"git.infra.nkode.tech/dkelly/nkode-core/entities"
"git.infra.nkode.tech/dkelly/nkode-core/models"
"git.infra.nkode.tech/dkelly/nkode-core/repository"
"git.infra.nkode.tech/dkelly/nkode-core/security"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"log"
"net/http"
"net/http/httptest"
"os"
"testing"
)
func TestNKodeAPI(t *testing.T) {
tr := NewTestRouter()
tr.Start()
defer func(tr *TestRouter) {
err := tr.Stop()
assert.NoError(t, err)
}(tr)
// *** Create New Customer with invalid access token ***
customerID, err := tr.CreateNewCustomerDefaultPolicy()
assert.NoError(t, err)
attrPerKey := 9
numKeys := 6
userEmail := "test_username" + security.GenerateNonSecureRandomString(12) + "@example.com"
// *** Signup ***
resp, status, err := tr.Signup(customerID, attrPerKey, numKeys, userEmail)
assert.NoError(t, err)
assert.Equal(t, 200, status)
passcodeLen := 4
userPasscode := resp.UserIdxInterface[:passcodeLen]
kpSet := entities.KeypadDimension{
AttrsPerKey: numKeys,
NumbOfKeys: numKeys,
}
setKeySelection, err := entities.SelectKeyByAttrIdx(resp.UserIdxInterface, userPasscode, kpSet)
assert.NoError(t, err)
// *** Set nKode ***
confirmInterface, status, err := tr.SetNKode(customerID, setKeySelection, resp.SessionID)
assert.NoError(t, err)
assert.Equal(t, 200, status)
confirmKeySelection, err := entities.SelectKeyByAttrIdx(confirmInterface, userPasscode, kpSet)
assert.NoError(t, err)
// *** Confirm nKode ***
status, err = tr.ConfirmNKode(customerID, confirmKeySelection, resp.SessionID)
assert.NoError(t, err)
assert.Equal(t, 200, status)
// *** Get Login Interface ***
loginInterface, status, err := tr.GetLoginInterface(userEmail, customerID)
assert.NoError(t, err)
assert.Equal(t, 200, status)
kp := entities.KeypadDimension{
AttrsPerKey: attrPerKey,
NumbOfKeys: numKeys,
}
loginKeySelection, err := entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, kp)
assert.NoError(t, err)
// *** Login ***
tokens, status, err := tr.Login(customerID, userEmail, loginKeySelection)
assert.NoError(t, err)
assert.Equal(t, 200, status)
assert.NotEmpty(t, tokens.AccessToken)
assert.NotEmpty(t, tokens.RefreshToken)
// *** Renew Attributes ***
status, err = tr.RenewAttributes(customerID)
assert.NoError(t, err)
assert.Equal(t, 200, status)
loginInterface, status, err = tr.GetLoginInterface(userEmail, customerID)
assert.NoError(t, err)
loginKeySelection, err = entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, kp)
assert.NoError(t, err)
tokens, status, err = tr.Login(customerID, userEmail, loginKeySelection)
assert.NoError(t, err)
// *** Test Forgot nKode ***
status, err = tr.ForgotNKode(customerID, userEmail)
assert.NoError(t, err)
assert.Equal(t, 200, status)
// *** Test Reset nKode ***
nkodeResetJwt, err := security.ResetNKodeToken(userEmail, customerID)
assert.NoError(t, err)
resetResp, status, err := tr.Reset(customerID, attrPerKey, numKeys, userEmail, nkodeResetJwt)
assert.NoError(t, err)
assert.Equal(t, 200, status)
assert.NotEmpty(t, resetResp.SessionID)
userPasscode = resetResp.UserIdxInterface[:passcodeLen]
setKeySelection, err = entities.SelectKeyByAttrIdx(resetResp.UserIdxInterface, userPasscode, kpSet)
assert.NoError(t, err)
confirmInterface, status, err = tr.SetNKode(customerID, setKeySelection, resetResp.SessionID)
assert.NoError(t, err)
assert.Equal(t, 200, status)
confirmKeySelection, err = entities.SelectKeyByAttrIdx(confirmInterface, userPasscode, kpSet)
assert.NoError(t, err)
status, err = tr.ConfirmNKode(customerID, confirmKeySelection, resetResp.SessionID)
assert.NoError(t, err)
assert.Equal(t, 200, status)
loginInterface, status, err = tr.GetLoginInterface(userEmail, customerID)
assert.NoError(t, err)
assert.Equal(t, 200, status)
loginKeySelection, err = entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, kp)
assert.NoError(t, err)
tokens, status, err = tr.Login(customerID, userEmail, loginKeySelection)
assert.NoError(t, err)
assert.Equal(t, 200, status)
assert.NotEmpty(t, tokens.AccessToken)
assert.NotEmpty(t, tokens.RefreshToken)
// *** Test Reset nKode with invalid token ***
_, status, err = tr.Reset(customerID, attrPerKey, numKeys, userEmail, "invalid token")
assert.Error(t, err)
assert.Equal(t, 403, status)
}
type TestRouter struct {
router *gin.Engine
emailQueue *email.Queue
repo *repository.SqliteNKodeRepo
handler *NkodeHandler
}
func NewTestRouter() *TestRouter {
gin.SetMode(gin.TestMode)
router := gin.Default()
logger := log.Default()
ctx := context.Background()
dbPath := os.Getenv("TEST_DB")
repo, err := repository.NewSqliteNKodeRepo(ctx, dbPath)
if err != nil {
log.Fatal(err)
}
emailClient := email.TestEmailClient{}
emailQueue := email.NewEmailQueue(email.EmailQueueBufferSize, email.MaxEmailsPerSecond, &emailClient)
nkodeAPI := api.NewNKodeAPI(repo, emailQueue)
h := NkodeHandler{
API: nkodeAPI,
Logger: logger,
}
h.RegisterRoutes(router)
return &TestRouter{
handler: &h,
router: router,
emailQueue: emailQueue,
repo: repo,
}
}
func (r *TestRouter) Start() {
r.repo.Start()
r.emailQueue.Start()
}
func (r *TestRouter) Stop() error {
r.emailQueue.Stop()
return r.repo.Stop()
}
func (r *TestRouter) CreateNewCustomerDefaultPolicy() (string, error) {
customerId, err := r.handler.API.CreateNewCustomer(entities.NewDefaultNKodePolicy())
if err != nil {
return "", err
}
return customerId.String(), nil
}
func (r *TestRouter) Signup(
customerID string,
attrsPerKey int,
numberOfKeys int,
userEmail string,
) (*entities.SignupResetInterface, int, error) {
body := bytes.NewBufferString(fmt.Sprintf(
"customer_id=%s&attrs_per_key=%d&numb_of_keys=%d&email=%s",
customerID,
attrsPerKey,
numberOfKeys,
userEmail,
))
req := httptest.NewRequest(http.MethodPost, "/signup", body)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
r.router.ServeHTTP(rec, req)
var resp entities.SignupResetInterface
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
return nil, rec.Code, err
}
return &resp, rec.Code, nil
}
func (r *TestRouter) SetNKode(
customerID string,
selection []int,
sessionID string,
) ([]int, int, error) {
data := models.SetNKodePost{
CustomerID: customerID,
KeySelection: selection,
SessionID: sessionID,
}
body, err := json.Marshal(data)
if err != nil {
return nil, 0, err
}
req := httptest.NewRequest(http.MethodPost, "/set-nkode", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
r.router.ServeHTTP(rec, req)
var resp struct {
UserInterface []int `json:"user_interface"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
return nil, rec.Code, err
}
return resp.UserInterface, rec.Code, nil
}
func (r *TestRouter) ConfirmNKode(
customerID string,
selection entities.KeySelection,
sessionID string,
) (int, error) {
data := models.ConfirmNKodePost{
CustomerID: customerID,
KeySelection: selection,
SessionID: sessionID,
}
body, err := json.Marshal(data)
if err != nil {
return 0, err
}
req := httptest.NewRequest(http.MethodPost, "/confirm-nkode", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
r.router.ServeHTTP(rec, req)
return rec.Code, nil
}
func (r *TestRouter) GetLoginInterface(
userEmail string,
customerID string,
) (entities.LoginInterface, int, error) {
body := bytes.NewBufferString(fmt.Sprintf(
"email=%s&customer_id=%s",
userEmail,
customerID,
))
req := httptest.NewRequest(http.MethodPost, "/get-login-interface", body)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
r.router.ServeHTTP(rec, req)
var resp entities.LoginInterface
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
return entities.LoginInterface{}, rec.Code, err
}
return resp, rec.Code, nil
}
func (r *TestRouter) Login(
customerID string,
userEmail string,
selection []int,
) (security.AuthenticationTokens, int, error) {
data := models.LoginPost{
CustomerID: customerID,
UserEmail: userEmail,
KeySelection: selection,
}
body, err := json.Marshal(data)
if err != nil {
return security.AuthenticationTokens{}, 0, err
}
req := httptest.NewRequest(http.MethodPost, "/login", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
r.router.ServeHTTP(rec, req)
var resp security.AuthenticationTokens
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
return security.AuthenticationTokens{}, rec.Code, err
}
return resp, rec.Code, nil
}
func (r *TestRouter) RenewAttributes(
customerID string,
) (int, error) {
data := models.RenewAttributesPost{
CustomerID: customerID,
}
body, err := json.Marshal(data)
if err != nil {
return 0, err
}
req := httptest.NewRequest(http.MethodPost, "/renew-attributes", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
r.router.ServeHTTP(rec, req)
return rec.Code, nil
}
func (r *TestRouter) ForgotNKode(
customerID string,
userEmail string,
) (int, error) {
data := models.ForgotNKodePost{
CustomerID: customerID,
UserEmail: userEmail,
}
body, err := json.Marshal(data)
if err != nil {
return 0, err
}
req := httptest.NewRequest(http.MethodPost, "/forgot-nkode", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
r.router.ServeHTTP(rec, req)
return rec.Code, nil
}
func (r *TestRouter) Reset(
customerID string,
attrsPerKey int,
numberOfKeys int,
userEmail string,
resetAuthToken string,
) (*entities.SignupResetInterface, int, error) {
body := bytes.NewBufferString(fmt.Sprintf(
"customer_id=%s&attrs_per_key=%d&numb_of_keys=%d&email=%s",
customerID,
attrsPerKey,
numberOfKeys,
userEmail,
))
req := httptest.NewRequest(http.MethodPost, "/reset-nkode", body)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Authorization", "Bearer "+resetAuthToken)
rec := httptest.NewRecorder()
r.router.ServeHTTP(rec, req)
var resp entities.SignupResetInterface
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
return nil, rec.Code, err
}
return &resp, rec.Code, nil
}

38
memcache/forgot_nkode.go Normal file
View File

@@ -0,0 +1,38 @@
package memcache
import (
"git.infra.nkode.tech/dkelly/nkode-core/entities"
"github.com/patrickmn/go-cache"
"time"
)
const (
forgotExpiration = 5 * time.Minute
forgotCleanupInterval = 10 * time.Minute
)
type ForgotNKodeCache struct {
innerCache *cache.Cache
}
func NewForgotNKodeCache() ForgotNKodeCache {
forgotCache := cache.New(forgotExpiration, forgotCleanupInterval)
return ForgotNKodeCache{forgotCache}
}
func (f *ForgotNKodeCache) Set(userEmail entities.UserEmail, customerId entities.CustomerID) {
f.innerCache.Set(key(userEmail, customerId), true, forgotExpiration)
}
func (f *ForgotNKodeCache) Get(userEmail entities.UserEmail, customerId entities.CustomerID) bool {
_, found := f.innerCache.Get(key(userEmail, customerId))
return found
}
func (f *ForgotNKodeCache) Delete(userEmail entities.UserEmail, customerId entities.CustomerID) {
f.innerCache.Delete(key(userEmail, customerId))
}
func key(email entities.UserEmail, id entities.CustomerID) string {
return string(email) + id.String()
}

59
models/models.go Normal file
View File

@@ -0,0 +1,59 @@
package models
import "git.infra.nkode.tech/dkelly/nkode-core/entities"
type SetNKodeResp struct {
UserInterface []int `json:"user_interface"`
}
type RandomSvgInterfaceResp struct {
Svgs []string `form:"svgs" binding:"required"`
Colors []entities.RGBColor `form:"colors" binding:"required"`
}
type RefreshTokenResp struct {
AccessToken string `form:"access_token" binding:"required"`
}
type SignupPostBody struct {
CustomerID string `form:"customer_id"`
AttrsPerKey int `form:"attrs_per_key"`
NumbOfKeys int `form:"numb_of_keys"`
UserEmail string `form:"email"`
}
type SetNKodePost struct {
CustomerID string `json:"customer_id" binding:"required"`
KeySelection []int `json:"key_selection" binding:"required"`
SessionID string `json:"session_id" binding:"required"`
}
type ConfirmNKodePost struct {
CustomerID string `json:"customer_id" binding:"required"`
KeySelection []int `json:"key_selection" binding:"required"`
SessionID string `json:"session_id" binding:"required"`
}
type LoginInterfacePost struct {
UserEmail string `form:"email" binding:"required"`
CustomerID string `form:"customer_id" binding:"required"`
}
type LoginPost struct {
CustomerID string `form:"customer_id" binding:"required"`
UserEmail string `form:"email" binding:"required"`
KeySelection entities.KeySelection `form:"key_selection" binding:"required"`
}
type RenewAttributesPost struct {
CustomerID string `form:"customer_id" binding:"required"`
}
type ForgotNKodePost struct {
UserEmail string `form:"email" binding:"required"`
CustomerID string `form:"customer_id" binding:"required"`
}
type CreateNewCustomerResp struct {
CustomerID string `form:"customer_id" binding:"required"`
}

View File

@@ -5,16 +5,16 @@ import (
) )
type CustomerUserRepository interface { type CustomerUserRepository interface {
GetCustomer(entities.CustomerId) (*entities.Customer, error) GetCustomer(entities.CustomerID) (*entities.Customer, error)
GetUser(entities.UserEmail, entities.CustomerId) (*entities.User, error) GetUser(entities.UserEmail, entities.CustomerID) (*entities.User, error)
CreateCustomer(entities.Customer) error CreateCustomer(entities.Customer) error
WriteNewUser(entities.User) error WriteNewUser(entities.User) error
UpdateUserNKode(entities.User) error UpdateUserNKode(entities.User) error
UpdateUserInterface(entities.UserId, entities.UserInterface) error UpdateUserInterface(entities.UserID, entities.UserInterface) error
UpdateUserRefreshToken(entities.UserId, string) error UpdateUserRefreshToken(entities.UserID, string) error
Renew(entities.CustomerId) error Renew(entities.CustomerID) error
RefreshUserPasscode(entities.User, []int, entities.CustomerAttributes) error RefreshUserPasscode(entities.User, []int, entities.CustomerAttributes) error
RandomSvgInterface(entities.KeypadDimension) ([]string, error) RandomSvgInterface(entities.KeypadDimension) ([]string, error)
RandomSvgIdxInterface(entities.KeypadDimension) (entities.SvgIdInterface, error) RandomSvgIdxInterface(entities.KeypadDimension) (entities.SvgIDInterface, error)
GetSvgStringInterface(entities.SvgIdInterface) ([]string, error) GetSvgStringInterface(entities.SvgIDInterface) ([]string, error)
} }

View File

@@ -5,7 +5,6 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"git.infra.nkode.tech/dkelly/nkode-core/config"
"git.infra.nkode.tech/dkelly/nkode-core/entities" "git.infra.nkode.tech/dkelly/nkode-core/entities"
"git.infra.nkode.tech/dkelly/nkode-core/security" "git.infra.nkode.tech/dkelly/nkode-core/security"
"git.infra.nkode.tech/dkelly/nkode-core/sqlc" "git.infra.nkode.tech/dkelly/nkode-core/sqlc"
@@ -15,19 +14,35 @@ import (
"log" "log"
) )
type SqliteRepository struct { type SqliteNKodeRepo struct {
Queue *sqlc.Queue Queue *sqlc.Queue
ctx context.Context ctx context.Context
} }
func NewSqliteRepository(queue *sqlc.Queue, ctx context.Context) SqliteRepository { func NewSqliteNKodeRepo(ctx context.Context, dbPath string) (*SqliteNKodeRepo, error) {
return SqliteRepository{ sqliteDb, err := sqlc.OpenSqliteDb(dbPath)
if err != nil {
return nil, err
}
queue, err := sqlc.NewQueue(sqliteDb, ctx)
if err != nil {
return nil, err
}
return &SqliteNKodeRepo{
Queue: queue, Queue: queue,
ctx: ctx, ctx: ctx,
} }, nil
} }
func (d *SqliteRepository) CreateCustomer(c entities.Customer) error { func (d *SqliteNKodeRepo) Start() {
d.Queue.Start()
}
func (d *SqliteNKodeRepo) Stop() error {
return d.Queue.Stop()
}
func (d *SqliteNKodeRepo) CreateCustomer(c entities.Customer) error {
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(sqlc.CreateCustomerParams) params, ok := args.(sqlc.CreateCustomerParams)
if !ok { if !ok {
@@ -39,7 +54,7 @@ func (d *SqliteRepository) CreateCustomer(c entities.Customer) error {
return d.Queue.EnqueueWriteTx(queryFunc, c.ToSqlcCreateCustomerParams()) return d.Queue.EnqueueWriteTx(queryFunc, c.ToSqlcCreateCustomerParams())
} }
func (d *SqliteRepository) WriteNewUser(u entities.User) error { func (d *SqliteNKodeRepo) WriteNewUser(u entities.User) error {
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(sqlc.CreateUserParams) params, ok := args.(sqlc.CreateUserParams)
if !ok { if !ok {
@@ -55,11 +70,11 @@ func (d *SqliteRepository) WriteNewUser(u entities.User) error {
} }
// Map entities.User to CreateUserParams // Map entities.User to CreateUserParams
params := sqlc.CreateUserParams{ params := sqlc.CreateUserParams{
ID: uuid.UUID(u.Id).String(), ID: uuid.UUID(u.ID).String(),
Email: string(u.Email), Email: string(u.Email),
Renew: int64(renew), Renew: int64(renew),
RefreshToken: sql.NullString{String: u.RefreshToken, Valid: u.RefreshToken != ""}, RefreshToken: sql.NullString{String: u.RefreshToken, Valid: u.RefreshToken != ""},
CustomerID: uuid.UUID(u.CustomerId).String(), CustomerID: uuid.UUID(u.CustomerID).String(),
Code: u.EncipheredPasscode.Code, Code: u.EncipheredPasscode.Code,
Mask: u.EncipheredPasscode.Mask, Mask: u.EncipheredPasscode.Mask,
AttributesPerKey: int64(u.Kp.AttrsPerKey), AttributesPerKey: int64(u.Kp.AttrsPerKey),
@@ -71,13 +86,13 @@ func (d *SqliteRepository) WriteNewUser(u entities.User) error {
Salt: u.CipherKeys.Salt, Salt: u.CipherKeys.Salt,
MaxNkodeLen: int64(u.CipherKeys.MaxNKodeLen), MaxNkodeLen: int64(u.CipherKeys.MaxNKodeLen),
IdxInterface: security.IntArrToByteArr(u.Interface.IdxInterface), IdxInterface: security.IntArrToByteArr(u.Interface.IdxInterface),
SvgIDInterface: security.IntArrToByteArr(u.Interface.SvgId), SvgIDInterface: security.IntArrToByteArr(u.Interface.SvgID),
CreatedAt: sql.NullString{String: utils.TimeStamp(), Valid: true}, CreatedAt: sql.NullString{String: utils.TimeStamp(), Valid: true},
} }
return d.Queue.EnqueueWriteTx(queryFunc, params) return d.Queue.EnqueueWriteTx(queryFunc, params)
} }
func (d *SqliteRepository) UpdateUserNKode(u entities.User) error { func (d *SqliteNKodeRepo) UpdateUserNKode(u entities.User) error {
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(sqlc.UpdateUserParams) params, ok := args.(sqlc.UpdateUserParams)
if !ok { if !ok {
@@ -94,7 +109,7 @@ func (d *SqliteRepository) UpdateUserNKode(u entities.User) error {
Email: string(u.Email), Email: string(u.Email),
Renew: int64(renew), Renew: int64(renew),
RefreshToken: sql.NullString{String: u.RefreshToken, Valid: u.RefreshToken != ""}, RefreshToken: sql.NullString{String: u.RefreshToken, Valid: u.RefreshToken != ""},
CustomerID: uuid.UUID(u.CustomerId).String(), CustomerID: uuid.UUID(u.CustomerID).String(),
Code: u.EncipheredPasscode.Code, Code: u.EncipheredPasscode.Code,
Mask: u.EncipheredPasscode.Mask, Mask: u.EncipheredPasscode.Mask,
AttributesPerKey: int64(u.Kp.AttrsPerKey), AttributesPerKey: int64(u.Kp.AttrsPerKey),
@@ -106,12 +121,12 @@ func (d *SqliteRepository) UpdateUserNKode(u entities.User) error {
Salt: u.CipherKeys.Salt, Salt: u.CipherKeys.Salt,
MaxNkodeLen: int64(u.CipherKeys.MaxNKodeLen), MaxNkodeLen: int64(u.CipherKeys.MaxNKodeLen),
IdxInterface: security.IntArrToByteArr(u.Interface.IdxInterface), IdxInterface: security.IntArrToByteArr(u.Interface.IdxInterface),
SvgIDInterface: security.IntArrToByteArr(u.Interface.SvgId), SvgIDInterface: security.IntArrToByteArr(u.Interface.SvgID),
} }
return d.Queue.EnqueueWriteTx(queryFunc, params) return d.Queue.EnqueueWriteTx(queryFunc, params)
} }
func (d *SqliteRepository) UpdateUserInterface(id entities.UserId, ui entities.UserInterface) error { func (d *SqliteNKodeRepo) UpdateUserInterface(id entities.UserID, ui entities.UserInterface) error {
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(sqlc.UpdateUserInterfaceParams) params, ok := args.(sqlc.UpdateUserInterfaceParams)
if !ok { if !ok {
@@ -128,7 +143,7 @@ func (d *SqliteRepository) UpdateUserInterface(id entities.UserId, ui entities.U
return d.Queue.EnqueueWriteTx(queryFunc, params) return d.Queue.EnqueueWriteTx(queryFunc, params)
} }
func (d *SqliteRepository) UpdateUserRefreshToken(id entities.UserId, refreshToken string) error { func (d *SqliteNKodeRepo) UpdateUserRefreshToken(id entities.UserID, refreshToken string) error {
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(sqlc.UpdateUserRefreshTokenParams) params, ok := args.(sqlc.UpdateUserRefreshTokenParams)
if !ok { if !ok {
@@ -146,7 +161,7 @@ func (d *SqliteRepository) UpdateUserRefreshToken(id entities.UserId, refreshTok
return d.Queue.EnqueueWriteTx(queryFunc, params) return d.Queue.EnqueueWriteTx(queryFunc, params)
} }
func (d *SqliteRepository) RenewCustomer(renewParams sqlc.RenewCustomerParams) error { func (d *SqliteNKodeRepo) RenewCustomer(renewParams sqlc.RenewCustomerParams) error {
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(sqlc.RenewCustomerParams) params, ok := args.(sqlc.RenewCustomerParams)
if !ok { if !ok {
@@ -157,13 +172,12 @@ func (d *SqliteRepository) RenewCustomer(renewParams sqlc.RenewCustomerParams) e
return d.Queue.EnqueueWriteTx(queryFunc, renewParams) return d.Queue.EnqueueWriteTx(queryFunc, renewParams)
} }
func (d *SqliteRepository) Renew(id entities.CustomerId) error { func (d *SqliteNKodeRepo) Renew(id entities.CustomerID) error {
setXor, attrXor, err := d.renewCustomer(id) setXor, attrXor, err := d.renewCustomer(id)
if err != nil { if err != nil {
return err return err
} }
customerId := entities.CustomerIdToString(id) userRenewRows, err := d.Queue.Queries.GetUserRenew(d.ctx, id.String())
userRenewRows, err := d.Queue.Queries.GetUserRenew(d.ctx, customerId)
if err != nil { if err != nil {
return err return err
} }
@@ -178,8 +192,8 @@ func (d *SqliteRepository) Renew(id entities.CustomerId) error {
for _, row := range userRenewRows { for _, row := range userRenewRows {
user := entities.User{ user := entities.User{
Id: entities.UserIdFromString(row.ID), ID: entities.UserIDFromString(row.ID),
CustomerId: entities.CustomerId{}, CustomerID: entities.CustomerID{},
Email: "", Email: "",
EncipheredPasscode: entities.EncipheredNKode{}, EncipheredPasscode: entities.EncipheredNKode{},
Kp: entities.KeypadDimension{ Kp: entities.KeypadDimension{
@@ -201,7 +215,7 @@ func (d *SqliteRepository) Renew(id entities.CustomerId) error {
AlphaKey: security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey), AlphaKey: security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey),
SetKey: security.Uint64ArrToByteArr(user.CipherKeys.SetKey), SetKey: security.Uint64ArrToByteArr(user.CipherKeys.SetKey),
Renew: 1, Renew: 1,
ID: uuid.UUID(user.Id).String(), ID: uuid.UUID(user.ID).String(),
} }
if err = d.Queue.EnqueueWriteTx(queryFunc, params); err != nil { if err = d.Queue.EnqueueWriteTx(queryFunc, params); err != nil {
return err return err
@@ -210,7 +224,7 @@ func (d *SqliteRepository) Renew(id entities.CustomerId) error {
return nil return nil
} }
func (d *SqliteRepository) renewCustomer(id entities.CustomerId) ([]uint64, []uint64, error) { func (d *SqliteNKodeRepo) renewCustomer(id entities.CustomerID) ([]uint64, []uint64, error) {
customer, err := d.GetCustomer(id) customer, err := d.GetCustomer(id)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -230,7 +244,7 @@ func (d *SqliteRepository) renewCustomer(id entities.CustomerId) ([]uint64, []ui
params := sqlc.RenewCustomerParams{ params := sqlc.RenewCustomerParams{
AttributeValues: security.Uint64ArrToByteArr(customer.Attributes.AttrVals), AttributeValues: security.Uint64ArrToByteArr(customer.Attributes.AttrVals),
SetValues: security.Uint64ArrToByteArr(customer.Attributes.SetVals), SetValues: security.Uint64ArrToByteArr(customer.Attributes.SetVals),
ID: uuid.UUID(customer.Id).String(), ID: uuid.UUID(customer.ID).String(),
} }
if err = d.Queue.EnqueueWriteTx(queryFunc, params); err != nil { if err = d.Queue.EnqueueWriteTx(queryFunc, params); err != nil {
@@ -239,7 +253,7 @@ func (d *SqliteRepository) renewCustomer(id entities.CustomerId) ([]uint64, []ui
return setXor, attrXor, nil return setXor, attrXor, nil
} }
func (d *SqliteRepository) RefreshUserPasscode(user entities.User, passcodeIdx []int, customerAttr entities.CustomerAttributes) error { func (d *SqliteNKodeRepo) RefreshUserPasscode(user entities.User, passcodeIdx []int, customerAttr entities.CustomerAttributes) error {
if err := user.RefreshPasscode(passcodeIdx, customerAttr); err != nil { if err := user.RefreshPasscode(passcodeIdx, customerAttr); err != nil {
return err return err
} }
@@ -259,19 +273,30 @@ func (d *SqliteRepository) RefreshUserPasscode(user entities.User, passcodeIdx [
PassKey: security.Uint64ArrToByteArr(user.CipherKeys.PassKey), PassKey: security.Uint64ArrToByteArr(user.CipherKeys.PassKey),
MaskKey: security.Uint64ArrToByteArr(user.CipherKeys.MaskKey), MaskKey: security.Uint64ArrToByteArr(user.CipherKeys.MaskKey),
Salt: user.CipherKeys.Salt, Salt: user.CipherKeys.Salt,
ID: uuid.UUID(user.Id).String(), ID: uuid.UUID(user.ID).String(),
} }
return d.Queue.EnqueueWriteTx(queryFunc, params) return d.Queue.EnqueueWriteTx(queryFunc, params)
} }
func (d *SqliteRepository) GetCustomer(id entities.CustomerId) (*entities.Customer, error) { func (d *SqliteNKodeRepo) AddSvg(svg string) error {
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(string)
if !ok {
return fmt.Errorf("invalid argument type: expected AddSvg")
}
return q.AddSvg(ctx, params)
}
return d.Queue.EnqueueWriteTx(queryFunc, svg)
}
func (d *SqliteNKodeRepo) GetCustomer(id entities.CustomerID) (*entities.Customer, error) {
customer, err := d.Queue.Queries.GetCustomer(d.ctx, uuid.UUID(id).String()) customer, err := d.Queue.Queries.GetCustomer(d.ctx, uuid.UUID(id).String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &entities.Customer{ return &entities.Customer{
Id: id, ID: id,
NKodePolicy: entities.NKodePolicy{ NKodePolicy: entities.NKodePolicy{
MaxNkodeLen: int(customer.MaxNkodeLen), MaxNkodeLen: int(customer.MaxNkodeLen),
MinNkodeLen: int(customer.MinNkodeLen), MinNkodeLen: int(customer.MinNkodeLen),
@@ -284,10 +309,10 @@ func (d *SqliteRepository) GetCustomer(id entities.CustomerId) (*entities.Custom
}, nil }, nil
} }
func (d *SqliteRepository) GetUser(email entities.UserEmail, customerId entities.CustomerId) (*entities.User, error) { func (d *SqliteNKodeRepo) GetUser(email entities.UserEmail, customerID entities.CustomerID) (*entities.User, error) {
userRow, err := d.Queue.Queries.GetUser(d.ctx, sqlc.GetUserParams{ userRow, err := d.Queue.Queries.GetUser(d.ctx, sqlc.GetUserParams{
Email: string(email), Email: string(email),
CustomerID: uuid.UUID(customerId).String(), CustomerID: uuid.UUID(customerID).String(),
}) })
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
@@ -306,8 +331,8 @@ func (d *SqliteRepository) GetUser(email entities.UserEmail, customerId entities
renew = true renew = true
} }
user := entities.User{ user := entities.User{
Id: entities.UserIdFromString(userRow.ID), ID: entities.UserIDFromString(userRow.ID),
CustomerId: customerId, CustomerID: customerID,
Email: email, Email: email,
EncipheredPasscode: entities.EncipheredNKode{ EncipheredPasscode: entities.EncipheredNKode{
Code: userRow.Code, Code: userRow.Code,
@@ -325,7 +350,7 @@ func (d *SqliteRepository) GetUser(email entities.UserEmail, customerId entities
}, },
Interface: entities.UserInterface{ Interface: entities.UserInterface{
IdxInterface: security.ByteArrToIntArr(userRow.IdxInterface), IdxInterface: security.ByteArrToIntArr(userRow.IdxInterface),
SvgId: security.ByteArrToIntArr(userRow.SvgIDInterface), SvgID: security.ByteArrToIntArr(userRow.SvgIDInterface),
Kp: &kp, Kp: &kp,
}, },
Renew: renew, Renew: renew,
@@ -334,26 +359,46 @@ func (d *SqliteRepository) GetUser(email entities.UserEmail, customerId entities
return &user, nil return &user, nil
} }
func (d *SqliteRepository) RandomSvgInterface(kp entities.KeypadDimension) ([]string, error) { func (d *SqliteNKodeRepo) RandomSvgInterface(kp entities.KeypadDimension) ([]string, error) {
ids, err := d.getRandomIds(kp.TotalAttrs()) ids, err := d.getRandomIDs(kp.TotalAttrs())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return d.getSvgsById(ids) return d.getSvgsByID(ids)
} }
func (d *SqliteRepository) RandomSvgIdxInterface(kp entities.KeypadDimension) (entities.SvgIdInterface, error) { func (d *SqliteNKodeRepo) RandomSvgIdxInterface(kp entities.KeypadDimension) (entities.SvgIDInterface, error) {
return d.getRandomIds(kp.TotalAttrs()) return d.getRandomIDs(kp.TotalAttrs())
} }
func (d *SqliteRepository) GetSvgStringInterface(idxs entities.SvgIdInterface) ([]string, error) { func (d *SqliteNKodeRepo) GetSvgStringInterface(idxs entities.SvgIDInterface) ([]string, error) {
return d.getSvgsById(idxs) return d.getSvgsByID(idxs)
} }
func (d *SqliteRepository) getSvgsById(ids []int) ([]string, error) { // Is this even useful?
func (d *SqliteNKodeRepo) AddUserPermission(userEmail entities.UserEmail, customerID entities.CustomerID, permission entities.UserPermission) error {
user, err := d.GetUser(userEmail, customerID)
if err != nil {
return err
}
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(sqlc.AddUserPermissionParams)
if !ok {
return fmt.Errorf("invalid argument type: expected AddUserPermissionParams")
}
return q.AddUserPermission(ctx, params)
}
params := sqlc.AddUserPermissionParams{
UserID: user.ID.String(),
Permission: permission.String(),
}
return d.Queue.EnqueueWriteTx(queryFunc, params)
}
func (d *SqliteNKodeRepo) getSvgsByID(ids []int) ([]string, error) {
svgs := make([]string, len(ids)) svgs := make([]string, len(ids))
for idx, id := range ids { for idx, id := range ids {
svg, err := d.Queue.Queries.GetSvgId(d.ctx, int64(id)) svg, err := d.Queue.Queries.GetSvgID(d.ctx, int64(id))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -362,40 +407,13 @@ func (d *SqliteRepository) getSvgsById(ids []int) ([]string, error) {
return svgs, nil return svgs, nil
} }
func (d *SqliteRepository) getRandomIds(count int) ([]int, error) { func (d *SqliteNKodeRepo) getRandomIDs(count int) ([]int, error) {
tx, err := d.Queue.Db.Begin() totalRows, err := d.Queue.Queries.GetSvgCount(d.ctx)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
return nil, config.ErrSqliteTx
}
rows, err := tx.Query("SELECT COUNT(*) as count FROM svg_icon;")
if err != nil {
log.Print(err)
return nil, config.ErrSqliteTx
}
var tableLen int
if !rows.Next() {
return nil, config.ErrEmptySvgTable
}
if err = rows.Scan(&tableLen); err != nil {
log.Print(err)
return nil, config.ErrSqliteTx
}
perm, err := security.RandomPermutation(tableLen)
if err != nil {
return nil, err return nil, err
} }
perm, err := security.RandomPermutation(int(totalRows))
for idx := range perm {
perm[idx] += 1
}
if err = tx.Commit(); err != nil {
log.Print(err)
return nil, config.ErrSqliteTx
}
return perm[:count], nil return perm[:count], nil
} }

View File

@@ -3,7 +3,6 @@ package repository
import ( import (
"context" "context"
"git.infra.nkode.tech/dkelly/nkode-core/entities" "git.infra.nkode.tech/dkelly/nkode-core/entities"
"git.infra.nkode.tech/dkelly/nkode-core/sqlc"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"os" "os"
"testing" "testing"
@@ -11,20 +10,16 @@ import (
func TestNewSqliteDB(t *testing.T) { func TestNewSqliteDB(t *testing.T) {
dbPath := os.Getenv("TEST_DB") dbPath := os.Getenv("TEST_DB")
// sql_driver.MakeTables(dbFile)
ctx := context.Background() ctx := context.Background()
sqliteDb, err := sqlc.OpenSqliteDb(dbPath) sqliteDb, err := NewSqliteNKodeRepo(ctx, dbPath)
assert.NoError(t, err) assert.NoError(t, err)
sqliteDb.Start()
queue, err := sqlc.NewQueue(sqliteDb, ctx) defer func(t *testing.T, sqliteDb *SqliteNKodeRepo) {
err := sqliteDb.Stop()
assert.NoError(t, err) assert.NoError(t, err)
}(t, sqliteDb)
queue.Start() testSignupLoginRenew(t, sqliteDb)
defer queue.Stop() testSqliteDBRandomSvgInterface(t, sqliteDb)
db := NewSqliteRepository(queue, ctx)
assert.NoError(t, err)
testSignupLoginRenew(t, &db)
testSqliteDBRandomSvgInterface(t, &db)
} }
func testSignupLoginRenew(t *testing.T, db CustomerUserRepository) { func testSignupLoginRenew(t *testing.T, db CustomerUserRepository) {
@@ -33,24 +28,24 @@ func testSignupLoginRenew(t *testing.T, db CustomerUserRepository) {
assert.NoError(t, err) assert.NoError(t, err)
err = db.CreateCustomer(*customerOrig) err = db.CreateCustomer(*customerOrig)
assert.NoError(t, err) assert.NoError(t, err)
customer, err := db.GetCustomer(customerOrig.Id) customer, err := db.GetCustomer(customerOrig.ID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, customerOrig, customer) assert.Equal(t, customerOrig, customer)
username := "test_user@example.com" username := "test_user@example.com"
kp := entities.KeypadDefault kp := entities.KeypadDefault
passcodeIdx := []int{0, 1, 2, 3} passcodeIdx := []int{0, 1, 2, 3}
mockSvgInterface := make(entities.SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(entities.SvgIDInterface, kp.TotalAttrs())
ui, err := entities.NewUserInterface(&kp, mockSvgInterface) ui, err := entities.NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
userOrig, err := entities.NewUser(*customer, username, passcodeIdx, *ui, kp) userOrig, err := entities.NewUser(*customer, username, passcodeIdx, *ui, kp)
assert.NoError(t, err) assert.NoError(t, err)
err = db.WriteNewUser(*userOrig) err = db.WriteNewUser(*userOrig)
assert.NoError(t, err) assert.NoError(t, err)
user, err := db.GetUser(entities.UserEmail(username), customer.Id) user, err := db.GetUser(entities.UserEmail(username), customer.ID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, userOrig, user) assert.Equal(t, userOrig, user)
err = db.Renew(customer.Id) err = db.Renew(customer.ID)
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@@ -65,6 +65,7 @@ func NewAuthenticationTokens(username string, customerId uuid.UUID) (Authenticat
} }
func NewAccessClaim(username string, customerId uuid.UUID) jwt.RegisteredClaims { func NewAccessClaim(username string, customerId uuid.UUID) jwt.RegisteredClaims {
// TODO: CHANGE ISSUER TO BE URL
return jwt.RegisteredClaims{ return jwt.RegisteredClaims{
Subject: username, Subject: username,
Issuer: customerId.String(), Issuer: customerId.String(),
@@ -85,6 +86,10 @@ func ParseRestNKodeToken(resetNKodeToken string) (*ResetNKodeClaims, error) {
return parseJwt[*ResetNKodeClaims](resetNKodeToken, &ResetNKodeClaims{}) return parseJwt[*ResetNKodeClaims](resetNKodeToken, &ResetNKodeClaims{})
} }
func ParseResetNKodeClaim(token string) (*ResetNKodeClaims, error) {
return parseJwt[*ResetNKodeClaims](token, &ResetNKodeClaims{})
}
func parseJwt[T *ResetNKodeClaims | *jwt.RegisteredClaims](tokenStr string, claim jwt.Claims) (T, error) { func parseJwt[T *ResetNKodeClaims | *jwt.RegisteredClaims](tokenStr string, claim jwt.Claims) (T, error) {
token, err := jwt.ParseWithClaims(tokenStr, claim, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(tokenStr, claim, func(token *jwt.Token) (interface{}, error) {
return secret, nil return secret, nil
@@ -110,12 +115,12 @@ func ClaimExpired(claims jwt.RegisteredClaims) error {
return config.ErrClaimExpOrNil return config.ErrClaimExpOrNil
} }
func ResetNKodeToken(userEmail string, customerId uuid.UUID) (string, error) { func ResetNKodeToken(userEmail string, customerId string) (string, error) {
resetClaims := ResetNKodeClaims{ resetClaims := ResetNKodeClaims{
true, true,
jwt.RegisteredClaims{ jwt.RegisteredClaims{
Subject: userEmail, Subject: userEmail,
Issuer: customerId.String(), Issuer: customerId,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(resetNKodeTokenExp)), ExpiresAt: jwt.NewNumericDate(time.Now().Add(resetNKodeTokenExp)),
}, },
} }

View File

@@ -19,7 +19,7 @@ func TestJwtClaims(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, refreshToken.Subject, email) assert.Equal(t, refreshToken.Subject, email)
assert.NoError(t, ClaimExpired(*refreshToken)) assert.NoError(t, ClaimExpired(*refreshToken))
resetNKode, err := ResetNKodeToken(email, customerId) resetNKode, err := ResetNKodeToken(email, customerId.String())
assert.NoError(t, err) assert.NoError(t, err)
resetToken, err := ParseRestNKodeToken(resetNKode) resetToken, err := ParseRestNKodeToken(resetNKode)
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -268,8 +268,7 @@ func Choice[T any](items []T) T {
return items[r.Intn(len(items))] return items[r.Intn(len(items))]
} }
// GenerateRandomString creates a random string of a specified length. func GenerateNonSecureRandomString(length int) string {
func GenerateRandomString(length int) string {
charset := []rune("abcdefghijklmnopqrstuvwxyz0123456789") charset := []rune("abcdefghijklmnopqrstuvwxyz0123456789")
b := make([]rune, length) b := make([]rune, length)
for i := range b { for i := range b {

View File

@@ -6,4 +6,4 @@ sql:
gen: gen:
go: go:
package: "sqlc" package: "sqlc"
out: "./pkg/nkode-core/sqlc" out: "./sqlc"

View File

@@ -6,8 +6,43 @@ package sqlc
import ( import (
"database/sql" "database/sql"
"time"
) )
type AuthorizationCode struct {
ID int64
Code string
CodeChallenge string
CodeChallengeMethod string
UserID string
ClientID string
Scope sql.NullString
RedirectUri string
CreatedAt sql.NullTime
ExpiresAt time.Time
UsedAt sql.NullTime
}
type Client struct {
ID string
Name string
Owner string
CreatedAt sql.NullTime
}
type ClientApproval struct {
ID int64
UserID string
ClientID string
}
type ClientRedirect struct {
ID int64
Uri string
ClientID string
CreatedAt sql.NullTime
}
type Customer struct { type Customer struct {
ID string ID string
MaxNkodeLen int64 MaxNkodeLen int64
@@ -22,11 +57,29 @@ type Customer struct {
CreatedAt string CreatedAt string
} }
type Session struct {
ID string
UserID string
CreatedAt sql.NullTime
ExpiresAt time.Time
}
type SvgIcon struct { type SvgIcon struct {
ID int64 ID int64
Svg string Svg string
} }
type Token struct {
ID int64
TokenType string
TokenValue string
UserID string
ClientID string
Scope sql.NullString
CreatedAt sql.NullTime
ExpiresAt time.Time
}
type User struct { type User struct {
ID string ID string
Email string Email string
@@ -48,3 +101,9 @@ type User struct {
LastLogin interface{} LastLogin interface{}
CreatedAt sql.NullString CreatedAt sql.NullString
} }
type UserPermission struct {
ID int64
UserID string
Permission string
}

View File

@@ -8,8 +8,95 @@ package sqlc
import ( import (
"context" "context"
"database/sql" "database/sql"
"time"
) )
const addSvg = `-- name: AddSvg :exec
INSERT INTO svg_icon (svg) VALUES (?)
`
func (q *Queries) AddSvg(ctx context.Context, svg string) error {
_, err := q.db.ExecContext(ctx, addSvg, svg)
return err
}
const addUserPermission = `-- name: AddUserPermission :exec
INSERT INTO user_permission (user_id, permission) VALUES (?, ?)
`
type AddUserPermissionParams struct {
UserID string
Permission string
}
func (q *Queries) AddUserPermission(ctx context.Context, arg AddUserPermissionParams) error {
_, err := q.db.ExecContext(ctx, addUserPermission, arg.UserID, arg.Permission)
return err
}
const approveClient = `-- name: ApproveClient :exec
INSERT INTO client_approvals (user_id, client_id)
VALUES (?, ?)
`
type ApproveClientParams struct {
UserID string
ClientID string
}
func (q *Queries) ApproveClient(ctx context.Context, arg ApproveClientParams) error {
_, err := q.db.ExecContext(ctx, approveClient, arg.UserID, arg.ClientID)
return err
}
const clientApproved = `-- name: ClientApproved :one
SELECT id, user_id, client_id
FROM client_approvals
WHERE user_id = ? AND client_id = ?
`
type ClientApprovedParams struct {
UserID string
ClientID string
}
func (q *Queries) ClientApproved(ctx context.Context, arg ClientApprovedParams) (ClientApproval, error) {
row := q.db.QueryRowContext(ctx, clientApproved, arg.UserID, arg.ClientID)
var i ClientApproval
err := row.Scan(&i.ID, &i.UserID, &i.ClientID)
return i, err
}
const createAuthorizationCode = `-- name: CreateAuthorizationCode :exec
INSERT INTO authorization_codes (code, code_challenge, code_challenge_method, user_id, client_id, scope, redirect_uri, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`
type CreateAuthorizationCodeParams struct {
Code string
CodeChallenge string
CodeChallengeMethod string
UserID string
ClientID string
Scope sql.NullString
RedirectUri string
ExpiresAt time.Time
}
func (q *Queries) CreateAuthorizationCode(ctx context.Context, arg CreateAuthorizationCodeParams) error {
_, err := q.db.ExecContext(ctx, createAuthorizationCode,
arg.Code,
arg.CodeChallenge,
arg.CodeChallengeMethod,
arg.UserID,
arg.ClientID,
arg.Scope,
arg.RedirectUri,
arg.ExpiresAt,
)
return err
}
const createCustomer = `-- name: CreateCustomer :exec const createCustomer = `-- name: CreateCustomer :exec
INSERT INTO customer ( INSERT INTO customer (
id id
@@ -58,6 +145,79 @@ func (q *Queries) CreateCustomer(ctx context.Context, arg CreateCustomerParams)
return err return err
} }
const createOIDCClient = `-- name: CreateOIDCClient :exec
INSERT INTO clients (id, name, owner)
VALUES (?, ?, ?)
`
type CreateOIDCClientParams struct {
ID string
Name string
Owner string
}
func (q *Queries) CreateOIDCClient(ctx context.Context, arg CreateOIDCClientParams) error {
_, err := q.db.ExecContext(ctx, createOIDCClient, arg.ID, arg.Name, arg.Owner)
return err
}
const createRedirectURI = `-- name: CreateRedirectURI :exec
INSERT INTO client_redirects (uri, client_id)
VALUES (?, ?)
`
type CreateRedirectURIParams struct {
Uri string
ClientID string
}
func (q *Queries) CreateRedirectURI(ctx context.Context, arg CreateRedirectURIParams) error {
_, err := q.db.ExecContext(ctx, createRedirectURI, arg.Uri, arg.ClientID)
return err
}
const createSession = `-- name: CreateSession :exec
INSERT INTO sessions (id, user_id, expires_at)
VALUES (?, ?, ?)
`
type CreateSessionParams struct {
ID string
UserID string
ExpiresAt time.Time
}
func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) error {
_, err := q.db.ExecContext(ctx, createSession, arg.ID, arg.UserID, arg.ExpiresAt)
return err
}
const createToken = `-- name: CreateToken :exec
INSERT INTO tokens (token_type, token_value, user_id, client_id, scope, expires_at)
VALUES (?, ?, ?, ?, ?, ?)
`
type CreateTokenParams struct {
TokenType string
TokenValue string
UserID string
ClientID string
Scope sql.NullString
ExpiresAt time.Time
}
func (q *Queries) CreateToken(ctx context.Context, arg CreateTokenParams) error {
_, err := q.db.ExecContext(ctx, createToken,
arg.TokenType,
arg.TokenValue,
arg.UserID,
arg.ClientID,
arg.Scope,
arg.ExpiresAt,
)
return err
}
const createUser = `-- name: CreateUser :exec const createUser = `-- name: CreateUser :exec
INSERT INTO user ( INSERT INTO user (
id id
@@ -127,6 +287,130 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) error {
return err return err
} }
const deleteAuthCode = `-- name: DeleteAuthCode :exec
DELETE FROM authorization_codes
WHERE code = ?
`
func (q *Queries) DeleteAuthCode(ctx context.Context, code string) error {
_, err := q.db.ExecContext(ctx, deleteAuthCode, code)
return err
}
const deleteOldAuthCodes = `-- name: DeleteOldAuthCodes :exec
DELETE FROM authorization_codes
WHERE expires_at < CURRENT_TIMESTAMP
`
func (q *Queries) DeleteOldAuthCodes(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteOldAuthCodes)
return err
}
const deleteOldSessions = `-- name: DeleteOldSessions :exec
DELETE FROM sessions
WHERE expires_at < CURRENT_TIMESTAMP
`
func (q *Queries) DeleteOldSessions(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteOldSessions)
return err
}
const deleteOldTokens = `-- name: DeleteOldTokens :exec
DELETE FROM tokens
WHERE expires_at < CURRENT_TIMESTAMP
`
func (q *Queries) DeleteOldTokens(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteOldTokens)
return err
}
const deleteRedirectURI = `-- name: DeleteRedirectURI :exec
DELETE FROM client_redirects
WHERE uri = ? AND client_id = ?
`
type DeleteRedirectURIParams struct {
Uri string
ClientID string
}
func (q *Queries) DeleteRedirectURI(ctx context.Context, arg DeleteRedirectURIParams) error {
_, err := q.db.ExecContext(ctx, deleteRedirectURI, arg.Uri, arg.ClientID)
return err
}
const deleteSession = `-- name: DeleteSession :exec
DELETE FROM sessions
WHERE id = ?
`
func (q *Queries) DeleteSession(ctx context.Context, id string) error {
_, err := q.db.ExecContext(ctx, deleteSession, id)
return err
}
const getAuthorizationCode = `-- name: GetAuthorizationCode :one
SELECT id, code, code_challenge, code_challenge_method, user_id, client_id, scope, redirect_uri, created_at, expires_at, used_at
FROM authorization_codes
WHERE code = ?
`
func (q *Queries) GetAuthorizationCode(ctx context.Context, code string) (AuthorizationCode, error) {
row := q.db.QueryRowContext(ctx, getAuthorizationCode, code)
var i AuthorizationCode
err := row.Scan(
&i.ID,
&i.Code,
&i.CodeChallenge,
&i.CodeChallengeMethod,
&i.UserID,
&i.ClientID,
&i.Scope,
&i.RedirectUri,
&i.CreatedAt,
&i.ExpiresAt,
&i.UsedAt,
)
return i, err
}
const getClientRedirectURIs = `-- name: GetClientRedirectURIs :many
SELECT id, uri, client_id, created_at
FROM client_redirects
WHERE client_id = ?
`
func (q *Queries) GetClientRedirectURIs(ctx context.Context, clientID string) ([]ClientRedirect, error) {
rows, err := q.db.QueryContext(ctx, getClientRedirectURIs, clientID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ClientRedirect
for rows.Next() {
var i ClientRedirect
if err := rows.Scan(
&i.ID,
&i.Uri,
&i.ClientID,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getCustomer = `-- name: GetCustomer :one const getCustomer = `-- name: GetCustomer :one
SELECT SELECT
max_nkode_len max_nkode_len
@@ -168,6 +452,42 @@ func (q *Queries) GetCustomer(ctx context.Context, id string) (GetCustomerRow, e
return i, err return i, err
} }
const getOIDCClientByID = `-- name: GetOIDCClientByID :one
SELECT id, name, owner, created_at
FROM clients
WHERE id = ?
`
func (q *Queries) GetOIDCClientByID(ctx context.Context, id string) (Client, error) {
row := q.db.QueryRowContext(ctx, getOIDCClientByID, id)
var i Client
err := row.Scan(
&i.ID,
&i.Name,
&i.Owner,
&i.CreatedAt,
)
return i, err
}
const getSessionByID = `-- name: GetSessionByID :one
SELECT id, user_id, created_at, expires_at
FROM sessions
WHERE id = ?
`
func (q *Queries) GetSessionByID(ctx context.Context, id string) (Session, error) {
row := q.db.QueryRowContext(ctx, getSessionByID, id)
var i Session
err := row.Scan(
&i.ID,
&i.UserID,
&i.CreatedAt,
&i.ExpiresAt,
)
return i, err
}
const getSvgCount = `-- name: GetSvgCount :one const getSvgCount = `-- name: GetSvgCount :one
SELECT COUNT(*) as count FROM svg_icon SELECT COUNT(*) as count FROM svg_icon
` `
@@ -179,19 +499,41 @@ func (q *Queries) GetSvgCount(ctx context.Context) (int64, error) {
return count, err return count, err
} }
const getSvgId = `-- name: GetSvgId :one const getSvgID = `-- name: GetSvgID :one
SELECT svg SELECT svg
FROM svg_icon FROM svg_icon
WHERE id = ? WHERE id = ?
` `
func (q *Queries) GetSvgId(ctx context.Context, id int64) (string, error) { func (q *Queries) GetSvgID(ctx context.Context, id int64) (string, error) {
row := q.db.QueryRowContext(ctx, getSvgId, id) row := q.db.QueryRowContext(ctx, getSvgID, id)
var svg string var svg string
err := row.Scan(&svg) err := row.Scan(&svg)
return svg, err return svg, err
} }
const getTokenByValue = `-- name: GetTokenByValue :one
SELECT id, token_type, token_value, user_id, client_id, scope, created_at, expires_at
FROM tokens
WHERE token_value = ?
`
func (q *Queries) GetTokenByValue(ctx context.Context, tokenValue string) (Token, error) {
row := q.db.QueryRowContext(ctx, getTokenByValue, tokenValue)
var i Token
err := row.Scan(
&i.ID,
&i.TokenType,
&i.TokenValue,
&i.UserID,
&i.ClientID,
&i.Scope,
&i.CreatedAt,
&i.ExpiresAt,
)
return i, err
}
const getUser = `-- name: GetUser :one const getUser = `-- name: GetUser :one
SELECT SELECT
id id
@@ -259,6 +601,69 @@ func (q *Queries) GetUser(ctx context.Context, arg GetUserParams) (GetUserRow, e
return i, err return i, err
} }
const getUserClients = `-- name: GetUserClients :many
SELECT id, name, owner, created_at
FROM clients
WHERE owner = ?
`
// -------- go-oidc ----------
func (q *Queries) GetUserClients(ctx context.Context, owner string) ([]Client, error) {
rows, err := q.db.QueryContext(ctx, getUserClients, owner)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Client
for rows.Next() {
var i Client
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Owner,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getUserPermissions = `-- name: GetUserPermissions :many
SELECT permission FROM user_permission WHERE user_id = ?
`
func (q *Queries) GetUserPermissions(ctx context.Context, userID string) ([]string, error) {
rows, err := q.db.QueryContext(ctx, getUserPermissions, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []string
for rows.Next() {
var permission string
if err := rows.Scan(&permission); err != nil {
return nil, err
}
items = append(items, permission)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getUserRenew = `-- name: GetUserRenew :many const getUserRenew = `-- name: GetUserRenew :many
SELECT SELECT
id id
@@ -388,6 +793,21 @@ func (q *Queries) RenewUser(ctx context.Context, arg RenewUserParams) error {
return err return err
} }
const revokeClientApproval = `-- name: RevokeClientApproval :exec
DELETE FROM client_approvals
WHERE user_id = ? AND client_id = ?
`
type RevokeClientApprovalParams struct {
UserID string
ClientID string
}
func (q *Queries) RevokeClientApproval(ctx context.Context, arg RevokeClientApprovalParams) error {
_, err := q.db.ExecContext(ctx, revokeClientApproval, arg.UserID, arg.ClientID)
return err
}
const updateUser = `-- name: UpdateUser :exec const updateUser = `-- name: UpdateUser :exec
UPDATE user UPDATE user
SET renew = ? SET renew = ?

View File

@@ -10,11 +10,11 @@ import (
const writeBufferSize = 100 const writeBufferSize = 100
type SqlcGeneric func(*Queries, context.Context, any) error type GenericQuery func(*Queries, context.Context, any) error
type WriteTx struct { type WriteTx struct {
ErrChan chan error ErrChan chan error
Query SqlcGeneric Query GenericQuery
Args interface{} Args interface{}
} }
@@ -63,7 +63,7 @@ func (d *Queue) Stop() error {
return d.Db.Close() return d.Db.Close()
} }
func (d *Queue) EnqueueWriteTx(queryFunc SqlcGeneric, args any) error { func (d *Queue) EnqueueWriteTx(queryFunc GenericQuery, args any) error {
select { select {
case <-d.ctx.Done(): case <-d.ctx.Done():
return errors.New("database is shutting down") return errors.New("database is shutting down")

6
sqlite/embed.go Normal file
View File

@@ -0,0 +1,6 @@
package sqlite
import "embed"
//go:embed schema.sql
var FS embed.FS

View File

@@ -37,6 +37,9 @@ INSERT INTO user (
) )
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?); VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
-- name: AddSvg :exec
INSERT INTO svg_icon (svg) VALUES (?);
-- name: UpdateUser :exec -- name: UpdateUser :exec
UPDATE user UPDATE user
SET renew = ? SET renew = ?
@@ -127,10 +130,106 @@ SELECT
FROM user FROM user
WHERE user.email = ? AND user.customer_id = ?; WHERE user.email = ? AND user.customer_id = ?;
-- name: GetSvgId :one -- name: GetSvgID :one
SELECT svg SELECT svg
FROM svg_icon FROM svg_icon
WHERE id = ?; WHERE id = ?;
-- name: GetSvgCount :one -- name: GetSvgCount :one
SELECT COUNT(*) as count FROM svg_icon; SELECT COUNT(*) as count FROM svg_icon;
-- name: GetUserPermissions :many
SELECT permission FROM user_permission WHERE user_id = ?;
-- name: AddUserPermission :exec
INSERT INTO user_permission (user_id, permission) VALUES (?, ?);
---------- go-oidc ----------
-- name: GetUserClients :many
SELECT *
FROM clients
WHERE owner = ?;
-- name: GetOIDCClientByID :one
SELECT *
FROM clients
WHERE id = ?;
-- name: CreateOIDCClient :exec
INSERT INTO clients (id, name, owner)
VALUES (?, ?, ?);
-- name: CreateRedirectURI :exec
INSERT INTO client_redirects (uri, client_id)
VALUES (?, ?);
-- name: DeleteRedirectURI :exec
DELETE FROM client_redirects
WHERE uri = ? AND client_id = ?;
-- name: GetClientRedirectURIs :many
SELECT *
FROM client_redirects
WHERE client_id = ?;
-- name: GetAuthorizationCode :one
SELECT *
FROM authorization_codes
WHERE code = ?;
-- name: CreateAuthorizationCode :exec
INSERT INTO authorization_codes (code, code_challenge, code_challenge_method, user_id, client_id, scope, redirect_uri, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
-- name: DeleteOldAuthCodes :exec
DELETE FROM authorization_codes
WHERE expires_at < CURRENT_TIMESTAMP;
-- name: DeleteOldTokens :exec
DELETE FROM tokens
WHERE expires_at < CURRENT_TIMESTAMP;
-- name: DeleteOldSessions :exec
DELETE FROM sessions
WHERE expires_at < CURRENT_TIMESTAMP;
-- name: GetTokenByValue :one
SELECT *
FROM tokens
WHERE token_value = ?;
-- name: CreateToken :exec
INSERT INTO tokens (token_type, token_value, user_id, client_id, scope, expires_at)
VALUES (?, ?, ?, ?, ?, ?);
-- name: ApproveClient :exec
INSERT INTO client_approvals (user_id, client_id)
VALUES (?, ?);
-- name: ClientApproved :one
SELECT *
FROM client_approvals
WHERE user_id = ? AND client_id = ?;
-- name: DeleteAuthCode :exec
DELETE FROM authorization_codes
WHERE code = ?;
-- name: GetSessionByID :one
SELECT *
FROM sessions
WHERE id = ?;
-- name: CreateSession :exec
INSERT INTO sessions (id, user_id, expires_at)
VALUES (?, ?, ?);
-- name: DeleteSession :exec
DELETE FROM sessions
WHERE id = ?;
-- name: RevokeClientApproval :exec
DELETE FROM client_approvals
WHERE user_id = ? AND client_id = ?;

View File

@@ -55,3 +55,79 @@ CREATE TABLE IF NOT EXISTS svg_icon (
id INTEGER PRIMARY KEY AUTOINCREMENT id INTEGER PRIMARY KEY AUTOINCREMENT
,svg TEXT NOT NULL ,svg TEXT NOT NULL
); );
CREATE TABLE IF NOT EXISTS user_permission (
id INTEGER PRIMARY KEY AUTOINCREMENT
,user_id TEXT NOT NULL
,permission TEXT NOT NULL
,FOREIGN KEY (user_id) REFERENCES user(id)
,UNIQUE(user_id, permission)
);
---- go-oidc
CREATE TABLE IF NOT EXISTS clients (
id TEXT PRIMARY KEY
,name TEXT NOT NULL
,owner TEXT NOT NULL
,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
,FOREIGN KEY (owner) REFERENCES user (id)
,UNIQUE(name, owner)
);
CREATE TABLE IF NOT EXISTS client_redirects (
id INTEGER PRIMARY KEY AUTOINCREMENT
,uri TEXT NOT NULL
,client_id TEXT NOT NULL
,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
,FOREIGN KEY (client_id) REFERENCES clients (id)
,UNIQUE(uri, client_id)
);
CREATE TABLE IF NOT EXISTS authorization_codes (
id INTEGER PRIMARY KEY AUTOINCREMENT
,code TEXT NOT NULL UNIQUE
,code_challenge TEXT NOT NULL UNIQUE
,code_challenge_method TEXT NOT NULL CHECK (code_challenge_method IN ('S256', 'plain'))
,user_id TEXT NOT NULL
,client_id TEXT NOT NULL
,scope TEXT
,redirect_uri TEXT NOT NULL
,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
,expires_at DATETIME NOT NULL
,used_at DATETIME
,FOREIGN KEY (user_id) REFERENCES user (id)
,FOREIGN KEY (client_id) REFERENCES client (id)
);
CREATE TABLE IF NOT EXISTS tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT
,token_type TEXT NOT NULL CHECK (token_type IN ('access', 'refresh'))
,token_value TEXT NOT NULL UNIQUE
,user_id TEXT NOT NULL
,client_id TEXT NOT NULL
,scope TEXT
,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
,expires_at DATETIME NOT NULL
,FOREIGN KEY (user_id) REFERENCES user (id)
,FOREIGN KEY (client_id) REFERENCES clients (id)
);
CREATE TABLE IF NOT EXISTS client_approvals (
id INTEGER PRIMARY KEY AUTOINCREMENT
,user_id TEXT NOT NULL
,client_id TEXT NOT NULL
,UNIQUE(user_id, client_id)
,FOREIGN KEY (user_id) REFERENCES users (id)
,FOREIGN KEY (client_id) REFERENCES clients (id)
);
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY
,user_id TEXT NOT NULL
,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
,expires_at DATETIME NOT NULL
,FOREIGN KEY (user_id) REFERENCES user (id)
);