All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-runners (push) Has been skipped
CI/CD / test-go-pg (push) Successful in 2m48s
CI/CD / test-sqlite (push) Successful in 2m49s
CI/CD / build-and-deploy (push) Successful in 28s
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
126 lines
3.4 KiB
Go
126 lines
3.4 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"armature/models"
|
|
)
|
|
|
|
// APITokenStore implements store.APITokenStore for PostgreSQL.
|
|
type APITokenStore struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NewAPITokenStore creates a new PostgreSQL API token store.
|
|
func NewAPITokenStore(db *sql.DB) *APITokenStore {
|
|
return &APITokenStore{db: db}
|
|
}
|
|
|
|
func (s *APITokenStore) Create(ctx context.Context, token *models.APIToken) error {
|
|
permsJSON, err := json.Marshal(token.Permissions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.db.QueryRowContext(ctx,
|
|
`INSERT INTO api_tokens (user_id, name, token_hash, prefix, permissions, expires_at, created_by)
|
|
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7)
|
|
RETURNING id, created_at`,
|
|
token.UserID, token.Name, token.TokenHash, token.Prefix,
|
|
string(permsJSON), token.ExpiresAt, token.CreatedBy,
|
|
).Scan(&token.ID, &token.CreatedAt)
|
|
}
|
|
|
|
func (s *APITokenStore) GetByHash(ctx context.Context, tokenHash string) (*models.APIToken, error) {
|
|
row := s.db.QueryRowContext(ctx,
|
|
`SELECT id, user_id, name, token_hash, prefix, permissions, expires_at, last_used_at, created_at, created_by
|
|
FROM api_tokens
|
|
WHERE token_hash = $1 AND (expires_at IS NULL OR expires_at > NOW())`,
|
|
tokenHash)
|
|
|
|
var t models.APIToken
|
|
var permsJSON []byte
|
|
err := row.Scan(&t.ID, &t.UserID, &t.Name, &t.TokenHash, &t.Prefix,
|
|
&permsJSON, &t.ExpiresAt, &t.LastUsedAt, &t.CreatedAt, &t.CreatedBy)
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := json.Unmarshal(permsJSON, &t.Permissions); err != nil {
|
|
t.Permissions = []string{}
|
|
}
|
|
return &t, nil
|
|
}
|
|
|
|
func (s *APITokenStore) ListForUser(ctx context.Context, userID string) ([]models.APIToken, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT id, user_id, name, prefix, permissions, expires_at, last_used_at, created_at, created_by
|
|
FROM api_tokens
|
|
WHERE user_id = $1
|
|
ORDER BY created_at DESC`,
|
|
userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var tokens []models.APIToken
|
|
for rows.Next() {
|
|
var t models.APIToken
|
|
var permsJSON []byte
|
|
if err := rows.Scan(&t.ID, &t.UserID, &t.Name, &t.Prefix,
|
|
&permsJSON, &t.ExpiresAt, &t.LastUsedAt, &t.CreatedAt, &t.CreatedBy); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := json.Unmarshal(permsJSON, &t.Permissions); err != nil {
|
|
t.Permissions = []string{}
|
|
}
|
|
tokens = append(tokens, t)
|
|
}
|
|
if tokens == nil {
|
|
tokens = []models.APIToken{}
|
|
}
|
|
return tokens, nil
|
|
}
|
|
|
|
func (s *APITokenStore) Revoke(ctx context.Context, id, userID string) (int64, error) {
|
|
res, err := s.db.ExecContext(ctx,
|
|
`DELETE FROM api_tokens WHERE id = $1 AND user_id = $2`,
|
|
id, userID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.RowsAffected()
|
|
}
|
|
|
|
func (s *APITokenStore) RevokeByID(ctx context.Context, id string) (int64, error) {
|
|
res, err := s.db.ExecContext(ctx,
|
|
`DELETE FROM api_tokens WHERE id = $1`, id)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.RowsAffected()
|
|
}
|
|
|
|
func (s *APITokenStore) CleanExpired(ctx context.Context) (int64, error) {
|
|
res, err := s.db.ExecContext(ctx,
|
|
`DELETE FROM api_tokens WHERE expires_at IS NOT NULL AND expires_at < NOW()`)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.RowsAffected()
|
|
}
|
|
|
|
func (s *APITokenStore) UpdateLastUsed(ctx context.Context, id string) error {
|
|
_, err := s.db.ExecContext(ctx,
|
|
`UPDATE api_tokens SET last_used_at = $1 WHERE id = $2`,
|
|
time.Now(), id)
|
|
return err
|
|
}
|