mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-02 11:31:57 +00:00
pkg/security no longer contains SQL. Every provider calls a store interface
from lookup, implemented by a procedure backend (Postgres stored procedures,
the default there) and a direct backend (dialect-driven SQL for postgres,
sqlite, mysql and mssql with configurable table and column names).
- add sectypes, lookup, lookup/{dialect,procedure,direct,backends,ddl,conformance}
- split totp and providers sub packages out of the core package
- replace SQLNames/TableNames/QueryMode with lookup.Config (see breaking_changes.md)
- direct backend now covers column/row security and API-key login
- move txsettings SQL to lookup.ApplyTxSettings; remove password.go
- move schema scripts under lookup/, add reference DDL per dialect
- add a shared conformance suite; run it on sqlite, and on Postgres in a
podman/docker container (RESOLVESPEC_TEST_CONTAINERS=1)
- fix procedure schema bugs found on real Postgres: duplicate p_data
parameter, JSON null arrays, expires_at timezone casts, passkey list
GROUP BY, missing resolvespec_passkey_login; accept zone-less timestamps
72 lines
2.6 KiB
Go
72 lines
2.6 KiB
Go
package direct
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/bitechdev/ResolveSpec/pkg/security/sectypes"
|
|
)
|
|
|
|
func TestKeysLifecycle(t *testing.T) {
|
|
ctx := context.Background()
|
|
db := newTestDB(t)
|
|
k := NewKeys(newTestBase(t, db, nil))
|
|
_, _ = db.Exec(`INSERT INTO users (username,email,password,is_active) VALUES ('u','u@x.io','x',1)`)
|
|
|
|
exp := time.Now().Add(time.Hour)
|
|
created, err := k.Create(ctx, sectypes.CreateKeyRequest{
|
|
UserID: 1, KeyType: sectypes.KeyTypeHeaderAPI, Name: "ci",
|
|
Scopes: []string{"read", "write"}, Meta: map[string]any{"env": "prod"}, ExpiresAt: &exp,
|
|
}, sectypes.HashKey("raw1"))
|
|
if err != nil || created.ID == 0 {
|
|
t.Fatalf("%+v %v", created, err)
|
|
}
|
|
// nil scopes/meta must not store the JSON text "null"
|
|
if _, err := k.Create(ctx, sectypes.CreateKeyRequest{UserID: 1, KeyType: sectypes.KeyTypeJWTSecret, Name: "j"}, sectypes.HashKey("raw2")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := k.Create(ctx, sectypes.CreateKeyRequest{UserID: 1, KeyType: sectypes.KeyTypeGenericAPI, Name: "old", ExpiresAt: ptr(time.Now().Add(-time.Hour))}, sectypes.HashKey("raw3")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
all, err := k.List(ctx, 1, "")
|
|
if err != nil || len(all) != 2 {
|
|
t.Fatalf("list all: %d %v", len(all), err)
|
|
}
|
|
one, _ := k.List(ctx, 1, sectypes.KeyTypeHeaderAPI)
|
|
if len(one) != 1 || one[0].Name != "ci" || len(one[0].Scopes) != 2 || one[0].Meta["env"] != "prod" || one[0].ExpiresAt == nil {
|
|
t.Fatalf("list typed: %+v", one)
|
|
}
|
|
if other, _ := k.List(ctx, 2, ""); len(other) != 0 {
|
|
t.Fatal("other user's keys listed")
|
|
}
|
|
|
|
got, err := k.Validate(ctx, sectypes.HashKey("raw1"), sectypes.KeyTypeHeaderAPI)
|
|
if err != nil || got.UserID != 1 || got.KeyHash != sectypes.HashKey("raw1") || got.LastUsedAt == nil {
|
|
t.Fatalf("%+v %v", got, err)
|
|
}
|
|
if _, err := k.Validate(ctx, sectypes.HashKey("raw1"), sectypes.KeyTypeGenericAPI); err == nil || err.Error() != "invalid or expired key" {
|
|
t.Fatalf("wrong type: %v", err)
|
|
}
|
|
if _, err := k.Validate(ctx, sectypes.HashKey("raw3"), ""); err == nil {
|
|
t.Fatal("expired key validated")
|
|
}
|
|
|
|
if _, err := k.Delete(ctx, 2, created.ID); err == nil || err.Error() != "key not found or already deleted" {
|
|
t.Fatalf("foreign delete: %v", err)
|
|
}
|
|
hash, err := k.Delete(ctx, 1, created.ID)
|
|
if err != nil || hash != sectypes.HashKey("raw1") {
|
|
t.Fatalf("%q %v", hash, err)
|
|
}
|
|
if _, err := k.Delete(ctx, 1, created.ID); err == nil {
|
|
t.Fatal("double delete succeeded")
|
|
}
|
|
if _, err := k.Validate(ctx, sectypes.HashKey("raw1"), ""); err == nil {
|
|
t.Fatal("deleted key validated")
|
|
}
|
|
}
|
|
|
|
func ptr[T any](v T) *T { return &v }
|