mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
refactor(schemas): sentinel first version
This commit is contained in:
parent
d0a44e93f8
commit
d065cbc623
5 changed files with 136 additions and 25 deletions
|
@ -0,0 +1,79 @@
|
|||
import { type CommonQueryMethods, sql } from 'slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
const getDatabaseName = async (pool: CommonQueryMethods) => {
|
||||
const { currentDatabase } = await pool.one<{ currentDatabase: string }>(sql`
|
||||
select current_database();
|
||||
`);
|
||||
|
||||
return currentDatabase.replaceAll('-', '_');
|
||||
};
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
const database = await getDatabaseName(pool);
|
||||
const baseRoleId = sql.identifier([`logto_tenant_${database}`]);
|
||||
|
||||
await pool.query(sql`
|
||||
create type sentinel_action_result as enum ('Success', 'Failed');
|
||||
|
||||
create type sentinel_decision as enum ('Undecided', 'Allowed', 'Blocked', 'Challenge');
|
||||
|
||||
create table sentinel_activities (
|
||||
tenant_id varchar(21) not null
|
||||
references tenants (id) on update cascade on delete cascade,
|
||||
id varchar(21) not null,
|
||||
/** The target that the action was performed on. */
|
||||
target_type varchar(32) /* @use SentinelActivityTargetType */ not null,
|
||||
/** The target hashed identifier. */
|
||||
target_hash varchar(64) not null,
|
||||
/** The action name that was performed. */
|
||||
action varchar(64) /* @use SentinelActivityAction */ not null,
|
||||
/** If the action was successful or not. */
|
||||
action_result sentinel_action_result not null,
|
||||
/** Additional payload data if any. */
|
||||
payload jsonb /* @use SentinelActivityPayload */ not null,
|
||||
/** The sentinel decision for the action. */
|
||||
decision sentinel_decision not null,
|
||||
/** The expiry date of the decision. */
|
||||
decision_expires_at timestamptz not null default(now()),
|
||||
/** The time the activity was created. */
|
||||
created_at timestamptz not null default(now()),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create index sentinel_activities__id
|
||||
on sentinel_activities (tenant_id, id);
|
||||
|
||||
create index sentinel_activities__target_type_target_hash_action_action_result_decision
|
||||
on sentinel_activities (tenant_id, target_type, target_hash, action, action_result, decision);
|
||||
|
||||
create trigger set_tenant_id before insert on sentinel_activities
|
||||
for each row execute procedure set_tenant_id();
|
||||
|
||||
alter table sentinel_activities enable row level security;
|
||||
|
||||
create policy sentinel_activities_tenant_id on sentinel_activities
|
||||
as restrictive
|
||||
using (tenant_id = (select id from tenants where db_user = current_user));
|
||||
|
||||
create policy sentinel_activities_modification on sentinel_activities
|
||||
using (true);
|
||||
|
||||
grant select, insert, update, delete on sentinel_activities to ${baseRoleId};
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
await pool.query(sql`
|
||||
drop policy sentinel_activities_tenant_id on sentinel_activities;
|
||||
drop policy sentinel_activities_modification on sentinel_activities;
|
||||
|
||||
drop table sentinel_activities;
|
||||
drop type sentinel_action_result;
|
||||
drop type sentinel_decision;
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -339,14 +339,6 @@ export const domainStatusGuard = z.nativeEnum(DomainStatus);
|
|||
|
||||
/* === Sentinel activities === */
|
||||
|
||||
/** The subject (actor) type of a sentinel activity. */
|
||||
export enum SentinelActivitySubjectType {
|
||||
User = 'User',
|
||||
App = 'App',
|
||||
Sentinel = 'Sentinel',
|
||||
}
|
||||
export const sentinelActivitySubjectTypeGuard = z.nativeEnum(SentinelActivitySubjectType);
|
||||
|
||||
/** The action target type of a sentinel activity. */
|
||||
export enum SentinelActivityTargetType {
|
||||
User = 'User',
|
||||
|
@ -357,19 +349,19 @@ export const sentinelActivityTargetTypeGuard = z.nativeEnum(SentinelActivityTarg
|
|||
/** The action type of a sentinel activity. */
|
||||
export enum SentinelActivityAction {
|
||||
/**
|
||||
* The subject tries to pass a verification for a target.
|
||||
* The subject tries to pass a verification by inputting a password.
|
||||
*
|
||||
* For example, a user (subject) who inputted a verification code or password for themselves
|
||||
* For example, a user (subject) who inputted a password (action) to authenticate themselves
|
||||
* (target).
|
||||
*/
|
||||
Verification = 'Verification',
|
||||
Password = 'Password',
|
||||
/**
|
||||
* The subject tries to block the target from passing a verification.
|
||||
* The subject tries to pass a verification by inputting a verification code.
|
||||
*
|
||||
* For example, the sentinel (subject) who blocked a user (target) from passing a verification
|
||||
* for 10 minutes.
|
||||
* For example, a user (subject) who inputted a verification code (action) to authenticate
|
||||
* themselves (target).
|
||||
*/
|
||||
BlockVerification = 'BlockVerification',
|
||||
VerificationCode = 'VerificationCode',
|
||||
}
|
||||
export const sentinelActivityActionGuard = z.nativeEnum(SentinelActivityAction);
|
||||
|
||||
|
|
|
@ -18,3 +18,4 @@ export * from './theme.js';
|
|||
export * from './cookie.js';
|
||||
export * from './dashboard.js';
|
||||
export * from './domain.js';
|
||||
export * from './sentinel.js';
|
||||
|
|
32
packages/schemas/src/types/sentinel.ts
Normal file
32
packages/schemas/src/types/sentinel.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { type SentinelDecision, type SentinelActivity } from '../db-entries/index.js';
|
||||
|
||||
/** The activity payload to be sent to the sentinel. */
|
||||
export type ActivityReport = Pick<
|
||||
SentinelActivity,
|
||||
'targetType' | 'targetHash' | 'action' | 'actionResult' | 'payload'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The sentinel class interface.
|
||||
*
|
||||
* Sentinels are responsible for accepting activity reports and making decisions based on them.
|
||||
*
|
||||
* For example, for a user sign-in activity, the sentinel might decide to:
|
||||
*
|
||||
* - Accept since the user uses the same device and location as usual;
|
||||
* - Require a MFA code since the user uses a new device or location;
|
||||
* - Block the user since the user tried to sign-in too many times with an incorrect password.
|
||||
*
|
||||
* The implementation should be privacy-aware and not store any personal identifiable information.
|
||||
*/
|
||||
export abstract class Sentinel {
|
||||
/**
|
||||
* Report an activity to the sentinel. The sentinel should make a decision based on the activity
|
||||
* (also the history, if needed) and return the result.
|
||||
*
|
||||
* @param activity The activity data to be reported.
|
||||
* @returns A Promise that resolves to the sentinel decision.
|
||||
* @see {@link SentinelDecision}
|
||||
*/
|
||||
abstract reportActivity(activity: ActivityReport): Promise<SentinelDecision>;
|
||||
}
|
|
@ -1,25 +1,32 @@
|
|||
create type sentinel_activity_result as enum ('Success', 'Failed');
|
||||
create type sentinel_action_result as enum ('Success', 'Failed');
|
||||
|
||||
create type sentinel_decision as enum ('Undecided', 'Allowed', 'Blocked', 'Challenge');
|
||||
|
||||
create table sentinel_activities (
|
||||
tenant_id varchar(21) not null
|
||||
references tenants (id) on update cascade on delete cascade,
|
||||
id varchar(21) not null,
|
||||
/** The subject (actor) that performed the action. */
|
||||
subject_type varchar(32) /* @use SentinelActivitySubjectType */ not null,
|
||||
/** The target that the action was performed on. */
|
||||
target_type varchar(32) /* @use SentinelActivityTargetType */ not null,
|
||||
/** The target identifier. */
|
||||
target_id varchar(21) not null
|
||||
references users (id) on update cascade on delete cascade,
|
||||
/** The related log id if any. */
|
||||
log_id varchar(21)
|
||||
references logs (id) on update cascade on delete cascade,
|
||||
/** The target hashed identifier. */
|
||||
target_hash varchar(64) not null,
|
||||
/** The action name that was performed. */
|
||||
action varchar(64) /* @use SentinelActivityAction */ not null,
|
||||
/** If the action was successful or not. */
|
||||
result sentinel_activity_result not null,
|
||||
action_result sentinel_action_result not null,
|
||||
/** Additional payload data if any. */
|
||||
payload jsonb /* @use SentinelActivityPayload */ not null,
|
||||
/** The sentinel decision for the action. */
|
||||
decision sentinel_decision not null,
|
||||
/** The expiry date of the decision. */
|
||||
decision_expires_at timestamptz not null default(now()),
|
||||
/** The time the activity was created. */
|
||||
created_at timestamptz not null default(now()),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create index sentinel_activities__id
|
||||
on sentinel_activities (tenant_id, id);
|
||||
|
||||
create index sentinel_activities__target_type_target_hash_action_action_result_decision
|
||||
on sentinel_activities (tenant_id, target_type, target_hash, action, action_result, decision);
|
||||
|
|
Loading…
Add table
Reference in a new issue