Files
sm-user-plugin/updates/202610260001_create_device_auth_sessions.go
Jakub Zych 4e3e5b05b5 feat: device authorization by QR or short code
PHP's device flow on golem15_user_device_auth_sessions (PHP's columns,
the token stored as its SHA-256, plus jwt_jti): POST /devices/initiate
answers a 64-character token, an XXXX-XXXX short code, the configurable
auth_url (golem15.user.device_auth.auth_url) and the expiry; a signed-in
user authorizes it with POST /devices/authorize (short_code or token,
one guarded pending-to-confirmed UPDATE); the device's first poll of
GET /devices/status/{token} after that receives a session JWT through one
guarded confirmed-to-used UPDATE, so exactly one poll gets it. Used and
revoked sessions never turn into expired; each initiate deletes a bounded
batch of stale sessions.
2026-10-26 10:14:48 +01:00

51 lines
2.1 KiB
Go

package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// deviceAuthSessionMigrations create golem15_user_device_auth_sessions, the
// QR and short-code device authorization sessions, with the PHP plugin's
// table and column names (the v2.1.0 to v2.7.1 migrations: short_code, a
// nullable user_id and expires_at). The token column holds the SHA-256 hex
// of the 64-character device token, never the token itself; jwt_jti is
// added: the jti of the session token the device received, so a revoke can
// blacklist it. session_id is kept for a Winter import and stays NULL.
var deviceAuthSessionMigrations = []*gormigrate.Migration{
{
ID: "202610260001_create_device_auth_sessions",
Migrate: func(tx *gorm.DB) error {
return tx.Exec(`CREATE TABLE golem15_user_device_auth_sessions (
id BIGSERIAL PRIMARY KEY,
token VARCHAR(64) NOT NULL UNIQUE,
short_code VARCHAR(9) NULL UNIQUE,
user_id INTEGER NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(16) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'confirmed', 'used', 'expired', 'revoked')),
expires_at TIMESTAMPTZ NULL,
device_ip VARCHAR(255) NULL,
device_user_agent TEXT NULL,
device_name VARCHAR(255) NULL,
auth_ip VARCHAR(255) NULL,
confirmed_at TIMESTAMPTZ NULL,
used_at TIMESTAMPTZ NULL,
session_id VARCHAR(255) NULL,
last_activity_at TIMESTAMPTZ NULL,
jwt_jti VARCHAR(64) NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX golem15_user_device_auth_sessions_user_id_index ON golem15_user_device_auth_sessions (user_id);
CREATE INDEX golem15_user_device_auth_sessions_status_index ON golem15_user_device_auth_sessions (status);
CREATE INDEX golem15_user_device_auth_sessions_expires_at_index ON golem15_user_device_auth_sessions (expires_at);
CREATE INDEX golem15_user_device_auth_sessions_jwt_jti_index ON golem15_user_device_auth_sessions (jwt_jti)`).Error
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS golem15_user_device_auth_sessions").Error
},
},
}
func init() { Register(deviceAuthSessionMigrations...) }