letopis 0.20.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT-CHEATSHEET.en.md +368 -0
- package/AGENT-CHEATSHEET.md +354 -0
- package/CHANGELOG.md +348 -0
- package/MIGRATION.md +190 -0
- package/README.en.md +1937 -0
- package/README.md +1493 -3466
- package/dist/acl.d.ts +26 -50
- package/dist/acl.js +22 -267
- package/dist/admin.d.ts +138 -0
- package/dist/admin.js +170 -0
- package/dist/auth.d.ts +120 -73
- package/dist/auth.js +121 -306
- package/dist/cache.d.ts +73 -0
- package/dist/cache.js +148 -0
- package/dist/chain.d.ts +124 -191
- package/dist/chain.js +369 -551
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +164 -0
- package/dist/demo/booking.d.ts +289 -0
- package/dist/demo/booking.js +159 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +70 -0
- package/dist/import.d.ts +179 -0
- package/dist/import.js +792 -0
- package/dist/index.d.ts +172 -26
- package/dist/index.js +304 -178
- package/dist/jsonschema.d.ts +22 -0
- package/dist/jsonschema.js +167 -0
- package/dist/load.d.ts +76 -0
- package/dist/load.js +884 -0
- package/dist/model.d.ts +166 -0
- package/dist/model.js +224 -0
- package/dist/ops.d.ts +7 -6
- package/dist/ops.js +7 -51
- package/dist/pglite.d.ts +22 -0
- package/dist/pglite.js +45 -0
- package/dist/registry.d.ts +57 -0
- package/dist/registry.js +82 -0
- package/dist/sql.d.ts +59 -142
- package/dist/sql.js +568 -654
- package/dist/sync.d.ts +31 -0
- package/dist/sync.js +108 -0
- package/dist/tx.d.ts +129 -8
- package/dist/tx.js +300 -73
- package/dist/typed.d.ts +97 -0
- package/dist/typed.js +1 -0
- package/dist/types.d.ts +71 -250
- package/dist/types.js +27 -108
- package/dist/up.d.ts +140 -47
- package/dist/up.js +339 -267
- package/dist/uuid.d.ts +21 -6
- package/dist/uuid.js +48 -64
- package/dist/validate.d.ts +24 -0
- package/dist/validate.js +251 -0
- package/dist/watch.d.ts +62 -0
- package/dist/watch.js +168 -0
- package/dist/write.d.ts +117 -74
- package/dist/write.js +658 -720
- package/llms.txt +26 -0
- package/package.json +49 -19
- package/sql/10-core.sql +136 -0
- package/sql/15-errors.sql +60 -0
- package/sql/20-context.sql +153 -0
- package/sql/30-validate.sql +423 -0
- package/sql/40-class.sql +259 -0
- package/sql/50-acl.sql +539 -0
- package/sql/60-write.sql +1369 -0
- package/sql/70-read.sql +245 -0
- package/sql/80-auth.sql +827 -0
- package/sql/90-time.sql +957 -0
- package/sql/95-seed.system.sql +178 -0
- package/sql/99-revision.sql +3 -0
- package/sql/README.md +56 -0
- package/sql/seed.booking.sql +39 -112
- package/dist/schema.d.ts +0 -15
- package/dist/schema.js +0 -351
- package/dist/sessions.d.ts +0 -32
- package/dist/sessions.js +0 -114
- package/dist/tables.d.ts +0 -105
- package/dist/tables.js +0 -248
- package/docker/Dockerfile +0 -40
- package/docker/start.sh +0 -18
- package/scripts/check-docs.mjs +0 -375
- package/scripts/gen-api-contract.mjs +0 -226
- package/scripts/gen-types.mjs +0 -350
- package/scripts/release-notes.mjs +0 -76
- package/scripts/schema-sync.mjs +0 -185
- package/sql/ddl.sql +0 -600
- package/sql/seed.auth.sql +0 -73
package/dist/admin.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { LetopisError } from './errors.js';
|
|
2
|
+
import { runTx } from './tx.js';
|
|
3
|
+
import { CHAIN_METHODS, DB_METHODS } from './types.js';
|
|
4
|
+
const SYSTEM_CLASSES = new Set(['Class', 'Account', 'member', 'Resource', 'rule']);
|
|
5
|
+
const one = async (a, text, params = []) => a.exec.run(async (q) => (await q.unsafe(text, params))[0]);
|
|
6
|
+
export async function verify(a, opts = {}) {
|
|
7
|
+
const r = await one(a, `select ${a.S}.verify($1::boolean) as v`, [opts.data === true]);
|
|
8
|
+
return r.v;
|
|
9
|
+
}
|
|
10
|
+
/** Ключ advisory-блокировки обслуживания: форма с двумя int4, своё пространство (19541). */
|
|
11
|
+
const MAINTAIN_LOCK = (schema) => [19541, schema];
|
|
12
|
+
/**
|
|
13
|
+
* Обслуживание: истёкшие сессии и коды, прореживание журнала пачками (каждая — своя транзакция,
|
|
14
|
+
* долгих блокировок нет), заполнение очереди сигналов. Из нескольких процессов работает один: пачка
|
|
15
|
+
* первым делом берёт транзакционную advisory-блокировку (pg_try_advisory_xact_lock), и та снимается
|
|
16
|
+
* с фиксацией пачки. Сеансовая блокировка за пулом в режиме транзакций (PgBouncer) оставалась бы на
|
|
17
|
+
* серверном соединении, которое пул отдаёт другим клиентам, — и обслуживание молча вставало бы
|
|
18
|
+
* (skipped у всех). Блокировку держит другой процесс: до первой пачки — skipped; между пачками —
|
|
19
|
+
* вызов останавливается (продолжит тот процесс), отчёт — о сделанном, more: true.
|
|
20
|
+
*/
|
|
21
|
+
export async function maintain(a, opts = {}) {
|
|
22
|
+
if (a.exec.explicit)
|
|
23
|
+
throw new LetopisError('invalid_query', 'maintain() — пачками в своих транзакциях, не внутри db.begin()');
|
|
24
|
+
const now = opts.now === undefined ? null : opts.now instanceof Date ? opts.now.toISOString() : opts.now;
|
|
25
|
+
const report = { skipped: false, sessions: 0, codes: 0, thinned: 0, batches: 0, more: false, queue: 0 };
|
|
26
|
+
const [k1, k2] = MAINTAIN_LOCK(a.schema);
|
|
27
|
+
const max = opts.batches ?? 100;
|
|
28
|
+
for (let i = 0; i < max; i++) {
|
|
29
|
+
// блокировка и пачка — одним оператором: case вычисляет maintain() только при взятой блокировке
|
|
30
|
+
const [{ r }] = await runTx(a.txc, (q) => q.unsafe(`select case when pg_try_advisory_xact_lock($1::int, hashtext($2::text))
|
|
31
|
+
then ${a.S}.maintain($3::text::timestamptz, $4::int) end as r`, [k1, k2, now, opts.limit ?? 10_000]));
|
|
32
|
+
if (r === null) {
|
|
33
|
+
if (i === 0)
|
|
34
|
+
return { ...report, skipped: true };
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
const b = r;
|
|
38
|
+
report.sessions += b.sessions;
|
|
39
|
+
report.codes += b.codes;
|
|
40
|
+
report.thinned += b.thinned;
|
|
41
|
+
report.queue = Number(b.queue);
|
|
42
|
+
report.more = b.more;
|
|
43
|
+
report.batches++;
|
|
44
|
+
if (!b.more)
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
const warn = queueWarning(report.queue);
|
|
48
|
+
if (warn)
|
|
49
|
+
console.warn(warn);
|
|
50
|
+
return report;
|
|
51
|
+
}
|
|
52
|
+
/** Предупреждение о заполненной очереди сигналов (§2.12): больше половины — зависший слушатель. */
|
|
53
|
+
export function queueWarning(queue) {
|
|
54
|
+
return queue > 0.5 ? `letopis: очередь сигналов PostgreSQL заполнена на ${(queue * 100).toFixed(0)} % — проверьте зависших слушателей` : null;
|
|
55
|
+
}
|
|
56
|
+
/** Интервал расписания: число мс или '30s', '15m', '1h', '1d'. */
|
|
57
|
+
export function parseInterval(v) {
|
|
58
|
+
if (typeof v === 'number')
|
|
59
|
+
return v;
|
|
60
|
+
const m = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/.exec(v.trim());
|
|
61
|
+
if (!m)
|
|
62
|
+
throw new LetopisError('invalid_data', `интервал ${v}: число мс или '30s', '15m', '1h', '1d'`);
|
|
63
|
+
const n = Number(m[1]);
|
|
64
|
+
return n * { ms: 1, s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2]];
|
|
65
|
+
}
|
|
66
|
+
/** Запуск maintain() по расписанию (connect({ maintain: '1h' })): ошибки — предупреждением. */
|
|
67
|
+
export function scheduleMaintain(run, every) {
|
|
68
|
+
const ms = parseInterval(every);
|
|
69
|
+
let busy = false;
|
|
70
|
+
const timer = setInterval(() => {
|
|
71
|
+
if (busy)
|
|
72
|
+
return;
|
|
73
|
+
busy = true;
|
|
74
|
+
run().catch((e) => console.warn(`letopis: maintain по расписанию: ${e.message}`)).finally(() => (busy = false));
|
|
75
|
+
}, ms);
|
|
76
|
+
timer.unref?.();
|
|
77
|
+
return () => clearInterval(timer);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Сброс (этап 6, п. 6). Предохранители — в базе: флаг allowReset установки, право letopis.reset,
|
|
81
|
+
* подтверждение именем схемы. tenant — пачками (длинная операция не держит горизонт подписок).
|
|
82
|
+
*/
|
|
83
|
+
export async function reset(a, opts) {
|
|
84
|
+
if (opts.level === 'tenant') {
|
|
85
|
+
if (a.exec.explicit)
|
|
86
|
+
throw new LetopisError('invalid_query', "reset({ level: 'tenant' }) — пачками в своих транзакциях, не внутри db.begin()");
|
|
87
|
+
let rows = 0;
|
|
88
|
+
let versions = 0;
|
|
89
|
+
for (;;) {
|
|
90
|
+
const [{ r }] = await runTx(a.txc, (q) => q.unsafe(`select ${a.S}.reset($1, $2, $3::uuid, 10000) as r`, [opts.level, opts.confirm, opts.tenant ?? null]));
|
|
91
|
+
const b = r;
|
|
92
|
+
rows += b.rows;
|
|
93
|
+
versions += b.versions;
|
|
94
|
+
if (b.done)
|
|
95
|
+
return { level: opts.level, tenant: b.tenant, rows, versions };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const r = await one(a, `select ${a.S}.reset($1, $2, $3::uuid) as r`, [opts.level, opts.confirm, opts.tenant ?? null]);
|
|
99
|
+
return r.r;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Обрезка истории (этап 6, п. 3): версии строже момента before удаляются, текущая — никогда; на место
|
|
103
|
+
* версии 1 встаёт запись trim. target — имя класса (с потомками), id или список id, строка ответа.
|
|
104
|
+
*/
|
|
105
|
+
export async function trimHistory(a, target, before) {
|
|
106
|
+
const at = before instanceof Date ? before.toISOString() : before;
|
|
107
|
+
let ids = null;
|
|
108
|
+
let classes = null;
|
|
109
|
+
if (typeof target === 'string' && a.registry().has(target)) {
|
|
110
|
+
classes = a.registry().family(target).map((c) => c.name);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const list = Array.isArray(target) ? target : [target];
|
|
114
|
+
ids = list.map((x) => (typeof x === 'string' ? x : x.id));
|
|
115
|
+
}
|
|
116
|
+
const r = await one(a, `select ${a.S}.trim_history($1::uuid[], $2::text[], $3::text::timestamptz) as n`, [ids, classes, at]);
|
|
117
|
+
return r.n;
|
|
118
|
+
}
|
|
119
|
+
/** Новый ключ класса (этап 6, п. 7): строки семейства переносятся на новые id. Возвращает их число. */
|
|
120
|
+
export async function rekeyClass(a, cls, key) {
|
|
121
|
+
const name = a.registry().resolve(cls).name;
|
|
122
|
+
const r = await one(a, `select ${a.S}.rekey_class($1, $2::jsonb) as n`, [name, key]);
|
|
123
|
+
return r.n;
|
|
124
|
+
}
|
|
125
|
+
/** Машинное описание модели арендатора вместе с метаданными классов (этап 6, п. 12). */
|
|
126
|
+
export function describe(a) {
|
|
127
|
+
const classes = a.registry().all().map((c) => ({
|
|
128
|
+
name: c.name,
|
|
129
|
+
kind: c.kind,
|
|
130
|
+
...(c.alias ? { alias: c.alias } : {}),
|
|
131
|
+
abstract: c.abstract,
|
|
132
|
+
...(c.ancestors[1] ? { parent: c.ancestors[1] } : {}),
|
|
133
|
+
ancestors: c.ancestors,
|
|
134
|
+
system: SYSTEM_CLASSES.has(c.name),
|
|
135
|
+
fields: (c.schema.properties ?? {}),
|
|
136
|
+
required: (c.schema.required ?? []),
|
|
137
|
+
ends: c.ends,
|
|
138
|
+
key: c.key,
|
|
139
|
+
history: c.history,
|
|
140
|
+
ownerDefault: c.ownerDefault,
|
|
141
|
+
...(c.cache ? { cache: c.cache } : {}),
|
|
142
|
+
meta: c.meta,
|
|
143
|
+
})).sort((x, y) => Number(x.system) - Number(y.system) || x.name.localeCompare(y.name));
|
|
144
|
+
return { schema: a.schema, tenant: a.tenant(), classes, methods: { chain: CHAIN_METHODS, db: DB_METHODS } };
|
|
145
|
+
}
|
|
146
|
+
/** Описание модели текстом для языковых моделей (llms.txt). */
|
|
147
|
+
export function describeText(d) {
|
|
148
|
+
const out = [`# letopis: модель схемы ${d.schema}`, '',
|
|
149
|
+
'Цепочка: db.Класс(фильтр).Связь().Класс().rows(); запись — create, update, upsert, delete; история — versions(), asOf(), withDeleted().', ''];
|
|
150
|
+
for (const c of d.classes) {
|
|
151
|
+
const title = typeof c.meta.title === 'string' ? ` — ${c.meta.title}` : '';
|
|
152
|
+
out.push(`## ${c.name}${c.alias ? ` (${c.alias})` : ''}${title}`);
|
|
153
|
+
out.push(`- вид: ${c.kind === 'hub' ? 'хаб (объект)' : 'связь'}${c.abstract ? ', абстрактный' : ''}${c.parent ? `, предок ${c.parent}` : ''}${c.system ? ', системный' : ''}`);
|
|
154
|
+
out.push(`- ключ: ${c.key.length ? c.key.join(', ') : 'нет (id выдаёт база)'}`);
|
|
155
|
+
const fields = Object.entries(c.fields).map(([k, v]) => {
|
|
156
|
+
const t = v.type;
|
|
157
|
+
return `${k}${c.required.includes(k) ? '*' : ''}: ${Array.isArray(t) ? t.join('|') : t ?? 'any'}`;
|
|
158
|
+
});
|
|
159
|
+
if (fields.length)
|
|
160
|
+
out.push(`- поля: ${fields.join(', ')}`);
|
|
161
|
+
const ends = Object.entries(c.ends).map(([k, v]) => {
|
|
162
|
+
const e = v;
|
|
163
|
+
return `${k}${e.required ? '*' : ''} → ${Array.isArray(e.class) ? e.class.join('|') : e.class}${e.many ? '[]' : ''}`;
|
|
164
|
+
});
|
|
165
|
+
if (ends.length)
|
|
166
|
+
out.push(`- концы: ${ends.join(', ')}`);
|
|
167
|
+
out.push('');
|
|
168
|
+
}
|
|
169
|
+
return out.join('\n');
|
|
170
|
+
}
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,123 +1,170 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import type { Tables } from './tables.js';
|
|
10
|
-
import { type SessionStore, type Sessions } from './sessions.js';
|
|
11
|
-
/** Код authenticator-а для секрета на момент времени (хелпер для тестов/сервисов). */
|
|
1
|
+
import type { Executor } from './tx.js';
|
|
2
|
+
/** Хэш пароля scrypt: `scrypt$N$r$p$соль$хэш` (формат 0.21). */
|
|
3
|
+
export declare function hashPassword(password: string): Promise<string>;
|
|
4
|
+
/** Проверка пароля по хэшу scrypt; другие форматы не проверяются. */
|
|
5
|
+
export declare function verifyHash(password: string, stored: string): Promise<boolean>;
|
|
6
|
+
export declare function base32Encode(buf: Uint8Array): string;
|
|
7
|
+
export declare function base32Decode(s: string): Buffer;
|
|
8
|
+
/** Код приложения-аутентификатора для секрета на момент времени (RFC 6238; помощник для тестов и сервисов). */
|
|
12
9
|
export declare function totpCode(secretBase32: string, atMs?: number): string;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
type AccountRef = string | {
|
|
11
|
+
id: string;
|
|
12
|
+
};
|
|
13
|
+
/** Успешный вход: аккаунт и токен новой сессии. */
|
|
14
|
+
export interface LoginResult {
|
|
15
|
+
account: string;
|
|
16
|
+
token: string;
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/** Кред без секретного материала. */
|
|
19
|
+
export interface Credential {
|
|
20
|
+
id: string;
|
|
21
|
+
kind: string;
|
|
22
|
+
/** Идентификатор (у TOTP — нет). */
|
|
23
|
+
ident: string | null;
|
|
24
|
+
confirmed: boolean;
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
meta: Record<string, unknown>;
|
|
27
|
+
created_at: string;
|
|
28
|
+
expires_at: string | null;
|
|
21
29
|
}
|
|
22
30
|
export interface AuthApi {
|
|
23
|
-
/** Пароль (
|
|
31
|
+
/** Пароль (и любой «логин/пароль»: kind — PASSWORD, EMAIL, …): создать или заменить. Смена гасит остальные сессии аккаунта. */
|
|
24
32
|
setPassword(a: {
|
|
25
|
-
account:
|
|
26
|
-
id: string;
|
|
27
|
-
};
|
|
33
|
+
account: AccountRef;
|
|
28
34
|
identifier: string;
|
|
29
35
|
password: string;
|
|
30
|
-
|
|
36
|
+
kind?: string;
|
|
31
37
|
confirmed?: boolean;
|
|
32
|
-
|
|
38
|
+
keepCurrent?: boolean;
|
|
39
|
+
}): Promise<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Вход по паролю — для сервиса с правом auth.password и auth.session. Неудача — null. С `totp` после
|
|
42
|
+
* верного пароля база проверяет код TOTP аккаунта (право auth.totp; счётчик неудач и блокировка — общие
|
|
43
|
+
* с verifyTotp): верный — сессия со способами [kind, 'TOTP'], неверный или повторный — null.
|
|
44
|
+
*/
|
|
33
45
|
verifyPassword(a: {
|
|
34
46
|
identifier: string;
|
|
35
47
|
password: string;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
kind?: string;
|
|
49
|
+
requireConfirmed?: boolean;
|
|
50
|
+
ttl?: number;
|
|
51
|
+
totp?: string;
|
|
52
|
+
}): Promise<LoginResult | null>;
|
|
53
|
+
/** Ключ API `lts_…`: отдаётся один раз. Себе сервис выпускает ключ сам по сессии своего ключа. */
|
|
39
54
|
issueApiKey(a: {
|
|
40
|
-
account:
|
|
41
|
-
id: string;
|
|
42
|
-
};
|
|
55
|
+
account: AccountRef;
|
|
43
56
|
name?: string;
|
|
57
|
+
ttl?: number;
|
|
44
58
|
}): Promise<{
|
|
45
59
|
key: string;
|
|
46
|
-
|
|
60
|
+
id: string;
|
|
47
61
|
}>;
|
|
48
|
-
verifyApiKey(key: string
|
|
49
|
-
/** Пара
|
|
62
|
+
verifyApiKey(key: string): Promise<LoginResult | null>;
|
|
63
|
+
/** Пара ключ + секрет: секрет отдаётся один раз. Себе сервис выпускает пару сам по сессии своего ключа. */
|
|
50
64
|
issueKeySecret(a: {
|
|
51
|
-
account:
|
|
52
|
-
id: string;
|
|
53
|
-
};
|
|
65
|
+
account: AccountRef;
|
|
54
66
|
name?: string;
|
|
55
67
|
}): Promise<{
|
|
56
68
|
key: string;
|
|
57
69
|
secret: string;
|
|
58
|
-
|
|
70
|
+
id: string;
|
|
59
71
|
}>;
|
|
60
|
-
verifyKeySecret(key: string, secret: string
|
|
61
|
-
/** TOTP
|
|
72
|
+
verifyKeySecret(key: string, secret: string): Promise<LoginResult | null>;
|
|
73
|
+
/** TOTP: секрет в base32 и ссылка otpauth:// для QR; фактор включает первая успешная проверка. */
|
|
62
74
|
enrollTotp(a: {
|
|
63
|
-
account:
|
|
64
|
-
id: string;
|
|
65
|
-
};
|
|
75
|
+
account: AccountRef;
|
|
66
76
|
issuer?: string;
|
|
67
77
|
label?: string;
|
|
68
78
|
}): Promise<{
|
|
69
79
|
secret: string;
|
|
70
80
|
uri: string;
|
|
71
|
-
credential: Credential;
|
|
72
81
|
}>;
|
|
73
|
-
/** Проверка 6-значного кода: окно ±window шагов (default 1), replay того же шага отбит. */
|
|
74
82
|
verifyTotp(a: {
|
|
75
|
-
account:
|
|
76
|
-
id: string;
|
|
77
|
-
};
|
|
83
|
+
account: AccountRef;
|
|
78
84
|
code: string;
|
|
79
85
|
window?: number;
|
|
80
86
|
}): Promise<boolean>;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
id: string;
|
|
84
|
-
}): Promise<boolean>;
|
|
85
|
-
/** Одноразовый код (email/SMS/reset — доставка на приложении): в БД sha256 + TTL + лимит попыток. */
|
|
87
|
+
totpEnabled(account: AccountRef): Promise<boolean>;
|
|
88
|
+
/** Одноразовый код для доставки сервисом (право auth.otp). */
|
|
86
89
|
issueOtp(a: {
|
|
87
|
-
account:
|
|
88
|
-
id: string;
|
|
89
|
-
};
|
|
90
|
+
account: AccountRef;
|
|
90
91
|
identifier: string;
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
kind?: string;
|
|
93
|
+
ttl?: number;
|
|
93
94
|
digits?: number;
|
|
95
|
+
maxAttempts?: number;
|
|
94
96
|
}): Promise<{
|
|
95
97
|
code: string;
|
|
96
|
-
credential: Credential;
|
|
97
98
|
}>;
|
|
98
|
-
/**
|
|
99
|
+
/** Вход по одноразовому коду: успех и перебор (лимит задал выпуск, 1–10, по умолчанию 5) сжигают код. */
|
|
99
100
|
verifyOtp(a: {
|
|
100
101
|
identifier: string;
|
|
101
102
|
code: string;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
/** Внешняя identity (TELEGRAM / GOOGLE / SSO-провайдер…): токен проверяет приложение, тут — связка. */
|
|
103
|
+
kind?: string;
|
|
104
|
+
}): Promise<LoginResult | null>;
|
|
105
|
+
/** Внешняя identity (TELEGRAM, GOOGLE, SSO): токен проверяет сервис, здесь — связка. */
|
|
106
106
|
link(a: {
|
|
107
|
-
account:
|
|
108
|
-
|
|
109
|
-
};
|
|
110
|
-
category: string;
|
|
107
|
+
account: AccountRef;
|
|
108
|
+
kind: string;
|
|
111
109
|
identifier: string;
|
|
112
110
|
meta?: Record<string, unknown>;
|
|
113
111
|
confirmed?: boolean;
|
|
114
|
-
}): Promise<
|
|
112
|
+
}): Promise<string>;
|
|
113
|
+
/** Вход по identity — для сервиса с правами auth.identity и auth.session; `totp` — как у verifyPassword. */
|
|
115
114
|
lookup(a: {
|
|
116
|
-
|
|
115
|
+
kind: string;
|
|
117
116
|
identifier: string;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
requireConfirmed?: boolean;
|
|
118
|
+
ttl?: number;
|
|
119
|
+
totp?: string;
|
|
120
|
+
}): Promise<LoginResult | null>;
|
|
121
|
+
/** Креды аккаунта без секретов. */
|
|
122
|
+
credentials(account: AccountRef): Promise<Credential[]>;
|
|
123
|
+
/**
|
|
124
|
+
* Удалить кред. Ключ API и пара ключ + секрет гасят только свои сессии, прочие креды — остальные сессии
|
|
125
|
+
* аккаунта; текущая гаснет, если keepCurrent: false. Свои ключи сервис отзывает сам; последний
|
|
126
|
+
* действующий ключ сервиса не отзывается (acl_denied).
|
|
127
|
+
*/
|
|
128
|
+
revokeCredential(id: string, opts?: {
|
|
129
|
+
keepCurrent?: boolean;
|
|
130
|
+
}): Promise<boolean>;
|
|
131
|
+
/** Признаки креда confirmed и enabled (право auth.credential). */
|
|
132
|
+
setFlags(id: string, flags: {
|
|
133
|
+
confirmed?: boolean;
|
|
134
|
+
enabled?: boolean;
|
|
135
|
+
}): Promise<boolean>;
|
|
136
|
+
/** Короткая сессия пользователя для другого процесса (право auth.session). */
|
|
137
|
+
sessionFor(account: AccountRef, opts?: {
|
|
138
|
+
ttl?: number;
|
|
139
|
+
}): Promise<string>;
|
|
140
|
+
/** Продлить текущую сессию; новый срок — от сейчас. */
|
|
141
|
+
refresh(opts?: {
|
|
142
|
+
ttl?: number;
|
|
143
|
+
}): Promise<string>;
|
|
144
|
+
/** Выход: погасить текущую сессию. */
|
|
145
|
+
revoke(): Promise<boolean>;
|
|
146
|
+
/** Выход везде: все сессии аккаунта (чужого — с правом auth.session). */
|
|
147
|
+
revokeAll(account?: AccountRef): Promise<number>;
|
|
148
|
+
/** Переключить арендатора сессии: собственный или по членству. */
|
|
149
|
+
switch(tenant: string): Promise<void>;
|
|
150
|
+
/** Арендаторы, доступные аккаунту сессии: собственный (owner) и по членству. */
|
|
151
|
+
tenants(): Promise<{
|
|
152
|
+
tenant: string;
|
|
153
|
+
roles: string[];
|
|
154
|
+
}[]>;
|
|
155
|
+
/**
|
|
156
|
+
* Удалить аккаунт (этап 6; accounts.purge 0.21): системный администратор или владелец его
|
|
157
|
+
* арендатора; себя и последнего владельца арендатора — нельзя. Данные арендатора стираются,
|
|
158
|
+
* владение строками в чужих арендаторах переходит им, сессии гаснут, креды удаляются.
|
|
159
|
+
*/
|
|
160
|
+
purgeAccount(account: AccountRef): Promise<{
|
|
161
|
+
account: string;
|
|
162
|
+
rows: number;
|
|
163
|
+
versions: number;
|
|
164
|
+
memberships: number;
|
|
165
|
+
owned: number;
|
|
166
|
+
}>;
|
|
121
167
|
}
|
|
122
|
-
|
|
168
|
+
/** Фасад db.auth. onSwitch — подключение перечитывает арендатора, реестр и пропуска прав. */
|
|
169
|
+
export declare function makeAuth(S: string, exec: Executor, onSwitch: () => Promise<void>, canSwitch: boolean): AuthApi;
|
|
123
170
|
export {};
|