From f0360259c996289f015bd159e08be3fe014ec3d4 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 25 Aug 2026 15:35:49 +0100 Subject: [PATCH] feat: API Key Generation --- internal/server/auth/apikey.go | 20 ++++++++++++++++++++ internal/server/auth/apikey_test.go | 17 +++++++++++++++++ internal/server/auth/doc.go | 2 -- internal/server/store/store.go | 14 +++++++++++--- migrations/0001_init.down.sql | 2 -- migrations/0001_init.up.sql | 12 +----------- 6 files changed, 49 insertions(+), 18 deletions(-) create mode 100644 internal/server/auth/apikey.go create mode 100644 internal/server/auth/apikey_test.go delete mode 100644 internal/server/auth/doc.go diff --git a/internal/server/auth/apikey.go b/internal/server/auth/apikey.go new file mode 100644 index 0000000..d552677 --- /dev/null +++ b/internal/server/auth/apikey.go @@ -0,0 +1,20 @@ +package auth + +import ( + "crypto/rand" + "encoding/base64" + "fmt" +) + +var Prefix = "potok_" + +const keyBytes = 32 + +func GenerateAPIKey() (key string, err error) { + raw := make([]byte, keyBytes) + if _, err := rand.Read(raw); err != nil { + return "", fmt.Errorf("auth: generate key: %w", err) + } + key = Prefix + base64.RawURLEncoding.EncodeToString(raw) + return key, nil +} diff --git a/internal/server/auth/apikey_test.go b/internal/server/auth/apikey_test.go new file mode 100644 index 0000000..3b7f8f8 --- /dev/null +++ b/internal/server/auth/apikey_test.go @@ -0,0 +1,17 @@ +package auth + +import ( + "strings" + "testing" +) + +func TestGenerateAPIKey(t *testing.T) { + valid, err := GenerateAPIKey() + if err != nil { + t.Fatalf("GenerateAPIKey() error: %v", err) + } + + if !strings.HasPrefix(valid, Prefix) { + t.Errorf("GenerateAPIKey() = %v, want %v", valid, Prefix+valid) + } +} diff --git a/internal/server/auth/doc.go b/internal/server/auth/doc.go deleted file mode 100644 index 6f06da5..0000000 --- a/internal/server/auth/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package auth issues and verifies API keys. -package auth diff --git a/internal/server/store/store.go b/internal/server/store/store.go index 2040ad5..8677a5c 100644 --- a/internal/server/store/store.go +++ b/internal/server/store/store.go @@ -8,6 +8,7 @@ import ( "time" "github.com/google/uuid" + "github.com/michaeltukdev/Potok/internal/server/auth" "golang.org/x/crypto/bcrypt" ) @@ -30,6 +31,7 @@ type User struct { Email string `json:"email"` PasswordHash string `json:"password_hash"` IsAdmin bool `json:"is_admin"` + APIKey string `json:"api_key"` CreatedAt time.Time `json:"created_at"` } @@ -87,18 +89,24 @@ func (s *Store) CreateUser(ctx context.Context, email, password string) (User, e return User{}, fmt.Errorf("store: create user: %w", err) } + key, err := auth.GenerateAPIKey() + if err != nil { + return User{}, fmt.Errorf("store: create user: %w", err) + } + user := User{ ID: uuid.NewString(), Email: email, PasswordHash: string(hash), + APIKey: key, } err = s.db.QueryRowContext(ctx, ` - INSERT INTO users (id, email, password_hash) - VALUES (?, ?, ?) + INSERT INTO users (id, email, password_hash, api_key) + VALUES (?, ?, ?, ?) ON CONFLICT (email) DO NOTHING RETURNING id, email, is_admin, created_at`, - user.ID, user.Email, user.PasswordHash, + user.ID, user.Email, user.PasswordHash, key, ).Scan(&user.ID, &user.Email, &user.IsAdmin, &user.CreatedAt) if err != nil { diff --git a/migrations/0001_init.down.sql b/migrations/0001_init.down.sql index 52c2800..8ba43b4 100644 --- a/migrations/0001_init.down.sql +++ b/migrations/0001_init.down.sql @@ -1,5 +1,3 @@ DROP INDEX IF EXISTS vaults_user_id_idx; DROP TABLE IF EXISTS vaults; -DROP INDEX IF EXISTS api_keys_user_id_idx; -DROP TABLE IF EXISTS api_keys; DROP TABLE IF EXISTS users; diff --git a/migrations/0001_init.up.sql b/migrations/0001_init.up.sql index e573e87..6e90e5e 100644 --- a/migrations/0001_init.up.sql +++ b/migrations/0001_init.up.sql @@ -3,20 +3,10 @@ CREATE TABLE users ( email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, is_admin INTEGER NOT NULL DEFAULT 0, + api_key TEXT UNIQUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE api_keys ( - id TEXT PRIMARY KEY, - user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE, - label TEXT NOT NULL, - key_hash BLOB NOT NULL UNIQUE, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - last_used_at TIMESTAMP -); - -CREATE INDEX api_keys_user_id_idx ON api_keys (user_id); - CREATE TABLE vaults ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,