feat(web): add auth and fix deployment checks
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 9s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 6s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Snapshot Admin Deployment / build-and-deploy (push) Failing after 2m30s
Deploy to Production / Build & Deploy to Production (push) Failing after 3m49s

This commit is contained in:
2026-07-01 13:02:10 +09:00
parent 3e4d545e01
commit 90bbb1860d
17 changed files with 445 additions and 53 deletions
@@ -30,6 +30,32 @@ namespace QuantEngine.Infrastructure.Data
);
");
// 0b. workspace_account
conn.Execute(@"
CREATE TABLE IF NOT EXISTS workspace_account (
ordinal INT NOT NULL,
username TEXT PRIMARY KEY,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'Admin',
is_active TEXT NOT NULL DEFAULT 'true',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_account_active ON workspace_account(is_active, username);
");
conn.Execute(@"
CREATE TABLE IF NOT EXISTS workspace_session (
session_token_hash TEXT PRIMARY KEY,
username TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'Admin',
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
revoked_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_workspace_session_username ON workspace_session(username, expires_at DESC);
");
// 1. collection_runs
conn.Execute(@"
CREATE TABLE IF NOT EXISTS collection_runs (
@@ -157,6 +183,16 @@ namespace QuantEngine.Infrastructure.Data
);
");
conn.Execute(@"
INSERT INTO quantengine.workspace_account (
ordinal, username, password_hash, role, is_active, created_at, updated_at
)
SELECT 1, 'admin', '8C6976E5B5410415BDE908BD4DEE15DFB167A9C873FC4BB8A81F6F2AB448A918', 'Admin', 'true', NOW()::text, NOW()::text
WHERE NOT EXISTS (
SELECT 1 FROM quantengine.workspace_account WHERE username = 'admin'
);
");
// 10. engine_history schema and tables
conn.Execute(@"
CREATE SCHEMA IF NOT EXISTS engine_history;
@@ -17,6 +17,89 @@ namespace QuantEngine.Infrastructure.Repositories
_connectionFactory = connectionFactory;
}
// Accounts
public async Task<IEnumerable<WorkspaceAccount>> GetAccountsAsync()
{
using var conn = _connectionFactory.CreateConnection();
return await conn.QueryAsync<WorkspaceAccount>(@"
SELECT ordinal, username as Username, password_hash as PasswordHash, role as Role,
is_active as IsActive, created_at as CreatedAt, updated_at as UpdatedAt
FROM quantengine.workspace_account
ORDER BY ordinal ASC"
);
}
public async Task<WorkspaceAccount?> GetAccountByUsernameAsync(string username)
{
using var conn = _connectionFactory.CreateConnection();
return await conn.QueryFirstOrDefaultAsync<WorkspaceAccount>(@"
SELECT ordinal, username as Username, password_hash as PasswordHash, role as Role,
is_active as IsActive, created_at as CreatedAt, updated_at as UpdatedAt
FROM quantengine.workspace_account
WHERE username = @Username",
new { Username = username }
);
}
public async Task<bool> UpsertAccountAsync(WorkspaceAccount account)
{
using var conn = _connectionFactory.CreateConnection();
var affected = await conn.ExecuteAsync(@"
INSERT INTO quantengine.workspace_account (ordinal, username, password_hash, role, is_active, created_at, updated_at)
VALUES (@Ordinal, @Username, @PasswordHash, @Role, @IsActive, @CreatedAt, @UpdatedAt)
ON CONFLICT (username) DO UPDATE SET
ordinal = EXCLUDED.ordinal,
password_hash = EXCLUDED.password_hash,
role = EXCLUDED.role,
is_active = EXCLUDED.is_active,
updated_at = EXCLUDED.updated_at",
account
);
return affected > 0;
}
public async Task<WorkspaceSession?> GetSessionByTokenHashAsync(string tokenHash)
{
using var conn = _connectionFactory.CreateConnection();
return await conn.QueryFirstOrDefaultAsync<WorkspaceSession>(@"
SELECT session_token_hash as SessionTokenHash, username as Username, role as Role,
created_at as CreatedAt, expires_at as ExpiresAt, revoked_at as RevokedAt
FROM quantengine.workspace_session
WHERE session_token_hash = @TokenHash",
new { TokenHash = tokenHash }
);
}
public async Task<bool> UpsertSessionAsync(WorkspaceSession session)
{
using var conn = _connectionFactory.CreateConnection();
var affected = await conn.ExecuteAsync(@"
INSERT INTO quantengine.workspace_session
(session_token_hash, username, role, created_at, expires_at, revoked_at)
VALUES
(@SessionTokenHash, @Username, @Role, @CreatedAt, @ExpiresAt, @RevokedAt)
ON CONFLICT (session_token_hash) DO UPDATE SET
username = EXCLUDED.username,
role = EXCLUDED.role,
expires_at = EXCLUDED.expires_at,
revoked_at = EXCLUDED.revoked_at",
session
);
return affected > 0;
}
public async Task<bool> RevokeSessionAsync(string tokenHash, string revokedAt)
{
using var conn = _connectionFactory.CreateConnection();
var affected = await conn.ExecuteAsync(@"
UPDATE quantengine.workspace_session
SET revoked_at = @RevokedAt
WHERE session_token_hash = @TokenHash",
new { TokenHash = tokenHash, RevokedAt = revokedAt }
);
return affected > 0;
}
// Settings
public async Task<IEnumerable<Setting>> GetSettingsAsync()
{