Merge pull request #5 from ai5590/codex/connect-ui-client-to-server-for-authentication-8njihj
Add AuthService, WS client and key-vault; implement session-based auth flow and update auth UI/pages
This commit is contained in:
commit
089146a137
@ -1,7 +1,7 @@
|
|||||||
import { navigate, getRoute, PRE_AUTH_PAGES } from './router.js?v=20260327192619';
|
import { navigate, getRoute, PRE_AUTH_PAGES } from './router.js?v=20260327192619';
|
||||||
import { renderToolbar } from './components/toolbar.js?v=20260327192619';
|
import { renderToolbar } from './components/toolbar.js?v=20260327192619';
|
||||||
import { renderPageLabel } from './components/page-label.js?v=20260327192619';
|
import { renderPageLabel } from './components/page-label.js?v=20260327192619';
|
||||||
import { state, togglePageLabel } from './state.js?v=20260327192619';
|
import { authService, authorizeSession, refreshSessions, state, togglePageLabel } from './state.js?v=20260327192619';
|
||||||
|
|
||||||
import * as startView from './pages/start-view.js?v=20260327192619';
|
import * as startView from './pages/start-view.js?v=20260327192619';
|
||||||
import * as entrySettingsView from './pages/entry-settings-view.js?v=20260327192619';
|
import * as entrySettingsView from './pages/entry-settings-view.js?v=20260327192619';
|
||||||
@ -113,10 +113,28 @@ function renderApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window.location.hash) {
|
async function tryAutoLogin() {
|
||||||
navigate(state.session.isAuthorized ? 'profile-view' : 'start-view');
|
if (!state.session.login || !state.session.sessionId) return;
|
||||||
} else {
|
try {
|
||||||
renderApp();
|
await authService.reconnect(state.entrySettings.shineServer);
|
||||||
|
const resumed = await authService.resumeSession(state.session.login, state.session.sessionId);
|
||||||
|
authorizeSession(resumed);
|
||||||
|
await refreshSessions();
|
||||||
|
} catch {
|
||||||
|
// silent fallback to auth screens
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('hashchange', renderApp);
|
async function init() {
|
||||||
|
await tryAutoLogin();
|
||||||
|
|
||||||
|
if (!window.location.hash) {
|
||||||
|
navigate(state.session.isAuthorized ? 'profile-view' : 'start-view');
|
||||||
|
} else {
|
||||||
|
renderApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('hashchange', renderApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
|||||||
@ -75,6 +75,7 @@ export function render({ navigate }) {
|
|||||||
try {
|
try {
|
||||||
await authService.reconnect(state.entrySettings.shineServer);
|
await authService.reconnect(state.entrySettings.shineServer);
|
||||||
const result = await authService.createSessionForExistingUser(state.loginDraft.login, state.loginDraft.password);
|
const result = await authService.createSessionForExistingUser(state.loginDraft.login, state.loginDraft.password);
|
||||||
|
await authService.persistSessionMaterial(state.loginDraft.login, result.sessionMaterial);
|
||||||
authorizeSession(result);
|
authorizeSession(result);
|
||||||
await refreshSessions();
|
await refreshSessions();
|
||||||
setAuthInfo('Успешный вход выполнен.');
|
setAuthInfo('Успешный вход выполнен.');
|
||||||
|
|||||||
@ -4,10 +4,11 @@ import {
|
|||||||
exportEd25519PublicKeyB64,
|
exportEd25519PublicKeyB64,
|
||||||
exportPkcs8B64,
|
exportPkcs8B64,
|
||||||
generateEd25519Pair,
|
generateEd25519Pair,
|
||||||
|
importPkcs8Ed25519,
|
||||||
randomBase64,
|
randomBase64,
|
||||||
signBase64,
|
signBase64,
|
||||||
} from './crypto-utils.js?v=20260327192619';
|
} from './crypto-utils.js?v=20260327192619';
|
||||||
import { saveEncryptedUserSecrets, saveSessionMaterial } from './key-vault.js?v=20260327192619';
|
import { loadSessionMaterial, saveEncryptedUserSecrets, saveSessionMaterial } from './key-vault.js?v=20260327192619';
|
||||||
|
|
||||||
const BCH_SUFFIX = '001';
|
const BCH_SUFFIX = '001';
|
||||||
|
|
||||||
@ -158,6 +159,48 @@ export class AuthService {
|
|||||||
await saveSessionMaterial(login, sessionMaterial);
|
await saveSessionMaterial(login, sessionMaterial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async resumeSession(login, preferredSessionId = '') {
|
||||||
|
const cleanLogin = (login || '').trim();
|
||||||
|
if (!cleanLogin) throw new Error('Нет login для авто-входа');
|
||||||
|
|
||||||
|
const sessionMaterial = await loadSessionMaterial(cleanLogin);
|
||||||
|
if (!sessionMaterial?.sessionId || !sessionMaterial?.sessionKey || !sessionMaterial?.sessionPrivPkcs8) {
|
||||||
|
throw new Error('На устройстве нет сохраненного ключа сессии');
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetSessionId = preferredSessionId || sessionMaterial.sessionId;
|
||||||
|
const privateKey = await importPkcs8Ed25519(sessionMaterial.sessionPrivPkcs8);
|
||||||
|
|
||||||
|
const challengeResp = await this.ws.request('SessionChallenge', { sessionId: targetSessionId });
|
||||||
|
if (challengeResp.status !== 200) throw opError('SessionChallenge', challengeResp);
|
||||||
|
|
||||||
|
const nonce = challengeResp?.payload?.nonce;
|
||||||
|
if (!nonce) throw new Error('SessionChallenge: не вернулся nonce');
|
||||||
|
|
||||||
|
const timeMs = Date.now();
|
||||||
|
const preimage = `SESSION_LOGIN:${targetSessionId}:${timeMs}:${nonce}`;
|
||||||
|
const signatureB64 = await signBase64(privateKey, preimage);
|
||||||
|
|
||||||
|
const loginResp = await this.ws.request('SessionLogin', {
|
||||||
|
sessionId: targetSessionId,
|
||||||
|
sessionKey: sessionMaterial.sessionKey,
|
||||||
|
timeMs,
|
||||||
|
signatureB64,
|
||||||
|
clientInfo: makeClientInfo(),
|
||||||
|
});
|
||||||
|
if (loginResp.status !== 200) throw opError('SessionLogin', loginResp);
|
||||||
|
|
||||||
|
const storagePwd = loginResp?.payload?.storagePwd;
|
||||||
|
if (!storagePwd) throw new Error('SessionLogin: не вернулся storagePwd');
|
||||||
|
|
||||||
|
return {
|
||||||
|
login: cleanLogin,
|
||||||
|
sessionId: targetSessionId,
|
||||||
|
storagePwd,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async listSessions() {
|
async listSessions() {
|
||||||
const response = await this.ws.request('ListSessions', {});
|
const response = await this.ws.request('ListSessions', {});
|
||||||
if (response.status !== 200) throw opError('ListSessions', response);
|
if (response.status !== 200) throw opError('ListSessions', response);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user