Files
passkey-auth/internal/database/database.go
T
junv 9427c36b8b feat: initial commit - WebAuthn passkey authentication service
- Complete WebAuthn/FIDO2 authentication implementation
- SQLite database with user and credential management
- Email-based user identification with allowlist support
- Admin approval workflow for new users
- Session management with secure cookies
- Docker containerization with Debian base for SQLite compatibility
- Kubernetes deployment manifests with nginx ingress support
- Web-based admin interface for user management
- Comprehensive documentation and deployment guides
- Standard open source project structure with CI/CD
2025-08-04 18:35:44 +10:00

203 lines
4.8 KiB
Go

package database
import (
"database/sql"
"time"
_ "github.com/mattn/go-sqlite3"
)
type DB struct {
conn *sql.DB
}
type User struct {
ID int `json:"id"`
Email string `json:"email"`
DisplayName string `json:"display_name"`
Approved bool `json:"approved"`
CreatedAt time.Time `json:"created_at"`
}
type Credential struct {
ID []byte `json:"id"`
UserID int `json:"user_id"`
PublicKey []byte `json:"public_key"`
AttestationType string `json:"attestation_type"`
AAGUID []byte `json:"aaguid"`
SignCount uint32 `json:"sign_count"`
CloneWarning bool `json:"clone_warning"`
CreatedAt time.Time `json:"created_at"`
}
func New(dbPath string) (*DB, error) {
conn, err := sql.Open("sqlite3", dbPath+"?_fk=1")
if err != nil {
return nil, err
}
db := &DB{conn: conn}
if err := db.migrate(); err != nil {
return nil, err
}
return db, nil
}
func (db *DB) Close() error {
return db.conn.Close()
}
func (db *DB) migrate() error {
queries := []string{
`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
display_name TEXT NOT NULL,
approved BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE TABLE IF NOT EXISTS credentials (
id BLOB PRIMARY KEY,
user_id INTEGER NOT NULL,
public_key BLOB NOT NULL,
attestation_type TEXT NOT NULL,
aaguid BLOB,
sign_count INTEGER DEFAULT 0,
clone_warning BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`,
`CREATE INDEX IF NOT EXISTS idx_credentials_user_id ON credentials(user_id)`,
}
for _, query := range queries {
if _, err := db.conn.Exec(query); err != nil {
return err
}
}
return nil
}
func (db *DB) CreateUser(email, displayName string) (*User, error) {
result, err := db.conn.Exec(
"INSERT INTO users (email, display_name) VALUES (?, ?)",
email, displayName,
)
if err != nil {
return nil, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, err
}
return db.GetUser(int(id))
}
func (db *DB) GetUser(id int) (*User, error) {
var user User
err := db.conn.QueryRow(
"SELECT id, email, display_name, approved, created_at FROM users WHERE id = ?",
id,
).Scan(&user.ID, &user.Email, &user.DisplayName, &user.Approved, &user.CreatedAt)
if err != nil {
return nil, err
}
return &user, nil
}
func (db *DB) GetUserByEmail(email string) (*User, error) {
var user User
err := db.conn.QueryRow(
"SELECT id, email, display_name, approved, created_at FROM users WHERE email = ?",
email,
).Scan(&user.ID, &user.Email, &user.DisplayName, &user.Approved, &user.CreatedAt)
if err != nil {
return nil, err
}
return &user, nil
}
func (db *DB) ListUsers() ([]*User, error) {
rows, err := db.conn.Query(
"SELECT id, email, display_name, approved, created_at FROM users ORDER BY created_at DESC",
)
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
var user User
if err := rows.Scan(&user.ID, &user.Email, &user.DisplayName, &user.Approved, &user.CreatedAt); err != nil {
return nil, err
}
users = append(users, &user)
}
return users, nil
}
func (db *DB) ApproveUser(id int) error {
_, err := db.conn.Exec("UPDATE users SET approved = TRUE WHERE id = ?", id)
return err
}
func (db *DB) DeleteUser(id int) error {
_, err := db.conn.Exec("DELETE FROM users WHERE id = ?", id)
return err
}
func (db *DB) SaveCredential(cred *Credential) error {
_, err := db.conn.Exec(
`INSERT INTO credentials (id, user_id, public_key, attestation_type, aaguid, sign_count, clone_warning)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
cred.ID, cred.UserID, cred.PublicKey, cred.AttestationType,
cred.AAGUID, cred.SignCount, cred.CloneWarning,
)
return err
}
func (db *DB) GetCredentialsByUserID(userID int) ([]*Credential, error) {
rows, err := db.conn.Query(
`SELECT id, user_id, public_key, attestation_type, aaguid, sign_count, clone_warning, created_at
FROM credentials WHERE user_id = ?`,
userID,
)
if err != nil {
return nil, err
}
defer rows.Close()
var credentials []*Credential
for rows.Next() {
var cred Credential
if err := rows.Scan(
&cred.ID, &cred.UserID, &cred.PublicKey, &cred.AttestationType,
&cred.AAGUID, &cred.SignCount, &cred.CloneWarning, &cred.CreatedAt,
); err != nil {
return nil, err
}
credentials = append(credentials, &cred)
}
return credentials, nil
}
func (db *DB) UpdateCredentialSignCount(credID []byte, signCount uint32) error {
_, err := db.conn.Exec(
"UPDATE credentials SET sign_count = ? WHERE id = ?",
signCount, credID,
)
return err
}