@everystack/auth 0.4.7 → 0.4.8
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/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/src/models/functions.ts +38 -1
- package/src/models/index.ts +2 -2
- package/src/sql.ts +31 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to `@everystack/auth` are documented here.
|
|
|
4
4
|
|
|
5
5
|
Format based on [Keep a Changelog](https://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## [0.4.8] - 2026-09-26
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- revoke sessions when users are blocked
|
|
12
|
+
|
|
13
|
+
## [0.4.7] - 2026-09-26
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- enforce bans on refresh and signin, check password before ban
|
|
18
|
+
- make the access token lifetime configurable
|
|
19
|
+
- signout revokes the presented refresh token
|
|
20
|
+
- refuse banned users on OAuth sign-in; signout never fails on revoke
|
|
21
|
+
|
|
22
|
+
### Documentation
|
|
23
|
+
|
|
24
|
+
- document signout revocation, accessTokenTtlSec and ban enforcement
|
|
25
|
+
|
|
7
26
|
## [0.4.6] - 2026-09-22
|
|
8
27
|
|
|
9
28
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/auth",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "JWT authentication handlers, password hashing, and React auth context",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Scalable Technology, Inc. <licensing@scalable.technology>",
|
package/src/models/functions.ts
CHANGED
|
@@ -39,8 +39,9 @@
|
|
|
39
39
|
* hand-patch a body here.
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
-
import { defineFunction, arg, can, sql } from '@everystack/model';
|
|
42
|
+
import { defineFunction, defineTrigger, arg, can, sql } from '@everystack/model';
|
|
43
43
|
import { DEFAULT_ACCESS_TOKEN_TTL_SEC, resolveAccessTokenTtl } from '../sql';
|
|
44
|
+
import { User } from './user';
|
|
44
45
|
|
|
45
46
|
export const changePasswordFn = defineFunction('auth.change_password', {
|
|
46
47
|
args: [arg('p_current_password', 'text'), arg('p_new_password', 'text')],
|
|
@@ -849,6 +850,36 @@ END;
|
|
|
849
850
|
`,
|
|
850
851
|
});
|
|
851
852
|
|
|
853
|
+
/** Removes every session as soon as an account becomes unavailable. */
|
|
854
|
+
export const revokeRefreshTokensOnUserBlockFn = defineFunction('auth.revoke_refresh_tokens_on_user_block', {
|
|
855
|
+
returns: 'trigger',
|
|
856
|
+
language: 'plpgsql',
|
|
857
|
+
security: 'definer',
|
|
858
|
+
searchPath: ['auth', 'public', 'pg_temp'],
|
|
859
|
+
body: sql`
|
|
860
|
+
BEGIN
|
|
861
|
+
DELETE FROM auth.refresh_tokens WHERE user_id = NEW.id;
|
|
862
|
+
RETURN NEW;
|
|
863
|
+
END;`,
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
/** The blocking transition is the durable revocation point; validation keeps its raise contract. */
|
|
867
|
+
export const revokeRefreshTokensOnUserBlockTrigger = defineTrigger('users_revoke_refresh_tokens_on_block', {
|
|
868
|
+
on: User,
|
|
869
|
+
timing: 'after',
|
|
870
|
+
events: ['update'],
|
|
871
|
+
of: ['banned_until', 'deleted_at'],
|
|
872
|
+
forEach: 'row',
|
|
873
|
+
condition: sql`
|
|
874
|
+
(NEW.deleted_at IS NOT NULL AND OLD.deleted_at IS NULL)
|
|
875
|
+
OR (
|
|
876
|
+
NEW.banned_until > now()
|
|
877
|
+
AND (OLD.banned_until IS NULL OR OLD.banned_until <= now())
|
|
878
|
+
)
|
|
879
|
+
`,
|
|
880
|
+
execute: revokeRefreshTokensOnUserBlockFn,
|
|
881
|
+
});
|
|
882
|
+
|
|
852
883
|
export const verifyJwtFn = defineFunction('auth.verify_jwt', {
|
|
853
884
|
args: [arg('p_token', 'text')],
|
|
854
885
|
returns: 'jsonb',
|
|
@@ -979,6 +1010,7 @@ export const coreFunctions = [hashPasswordFn, verifyPasswordFn, signJwtFn, verif
|
|
|
979
1010
|
/** The refresh-token family (RFC 6819 rotation). Always on. */
|
|
980
1011
|
export const refreshTokenFunctions = [
|
|
981
1012
|
issueRefreshTokenFn, validateRefreshTokenFn, revokeRefreshTokensFn, signOutFn, cleanupRefreshTokensFn,
|
|
1013
|
+
revokeRefreshTokensOnUserBlockFn,
|
|
982
1014
|
];
|
|
983
1015
|
|
|
984
1016
|
/** `auth.uid()` / `auth.role()` / `auth.jwt()` — called FROM RLS policies, so granted to the
|
|
@@ -1041,3 +1073,8 @@ export function authFunctions(opts: AuthFunctionOptions = {}) {
|
|
|
1041
1073
|
...(opts.jwtWrappers === true ? jwtWrapperFunctionsFor(opts.accessTokenTtlSec) : []),
|
|
1042
1074
|
];
|
|
1043
1075
|
}
|
|
1076
|
+
|
|
1077
|
+
/** The auth module's complete derived layer: functions plus lifecycle bindings. */
|
|
1078
|
+
export function authDerived(opts: AuthFunctionOptions = {}) {
|
|
1079
|
+
return [...authFunctions(opts), revokeRefreshTokensOnUserBlockTrigger];
|
|
1080
|
+
}
|
package/src/models/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { Device } from './device';
|
|
|
25
25
|
import { MfaFactor } from './mfa-factor';
|
|
26
26
|
import { MfaChallenge } from './mfa-challenge';
|
|
27
27
|
import { OneTimeToken } from './one-time-token';
|
|
28
|
-
import {
|
|
28
|
+
import { authDerived, type AuthFunctionOptions } from './functions';
|
|
29
29
|
|
|
30
30
|
export * from './functions';
|
|
31
31
|
|
|
@@ -51,7 +51,7 @@ export function defineAuthModule(opts: AuthFunctionOptions = {}): Module {
|
|
|
51
51
|
return defineModule({
|
|
52
52
|
models: authModels,
|
|
53
53
|
extensions: ['pgcrypto'],
|
|
54
|
-
derived:
|
|
54
|
+
derived: authDerived(opts),
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
package/src/sql.ts
CHANGED
|
@@ -358,6 +358,33 @@ $$;
|
|
|
358
358
|
|
|
359
359
|
REVOKE EXECUTE ON FUNCTION auth.validate_refresh_token FROM PUBLIC;`;
|
|
360
360
|
|
|
361
|
+
const REVOKE_REFRESH_TOKENS_ON_USER_BLOCK = `\
|
|
362
|
+
CREATE OR REPLACE FUNCTION auth.revoke_refresh_tokens_on_user_block()
|
|
363
|
+
RETURNS TRIGGER
|
|
364
|
+
LANGUAGE plpgsql
|
|
365
|
+
SECURITY DEFINER
|
|
366
|
+
SET search_path = auth, public, pg_temp
|
|
367
|
+
AS $$
|
|
368
|
+
BEGIN
|
|
369
|
+
DELETE FROM auth.refresh_tokens WHERE user_id = NEW.id;
|
|
370
|
+
RETURN NEW;
|
|
371
|
+
END;
|
|
372
|
+
$$;
|
|
373
|
+
|
|
374
|
+
REVOKE EXECUTE ON FUNCTION auth.revoke_refresh_tokens_on_user_block FROM PUBLIC;
|
|
375
|
+
|
|
376
|
+
CREATE TRIGGER users_revoke_refresh_tokens_on_block
|
|
377
|
+
AFTER UPDATE OF banned_until, deleted_at ON auth.users
|
|
378
|
+
FOR EACH ROW
|
|
379
|
+
WHEN (
|
|
380
|
+
(NEW.deleted_at IS NOT NULL AND OLD.deleted_at IS NULL)
|
|
381
|
+
OR (
|
|
382
|
+
NEW.banned_until > now()
|
|
383
|
+
AND (OLD.banned_until IS NULL OR OLD.banned_until <= now())
|
|
384
|
+
)
|
|
385
|
+
)
|
|
386
|
+
EXECUTE FUNCTION auth.revoke_refresh_tokens_on_user_block();`;
|
|
387
|
+
|
|
361
388
|
const REVOKE_REFRESH_TOKENS = `\
|
|
362
389
|
CREATE OR REPLACE FUNCTION auth.revoke_refresh_tokens(
|
|
363
390
|
p_user_id UUID,
|
|
@@ -1091,7 +1118,10 @@ export function generateCoreSQL(): string {
|
|
|
1091
1118
|
|
|
1092
1119
|
/** issue + validate + revoke + sign_out + cleanup refresh tokens (RFC 6819) */
|
|
1093
1120
|
export function generateRefreshTokenSQL(): string {
|
|
1094
|
-
return [
|
|
1121
|
+
return [
|
|
1122
|
+
ISSUE_REFRESH_TOKEN, '', VALIDATE_REFRESH_TOKEN, '', REVOKE_REFRESH_TOKENS_ON_USER_BLOCK, '',
|
|
1123
|
+
REVOKE_REFRESH_TOKENS, '', SIGN_OUT, '', CLEANUP_REFRESH_TOKENS,
|
|
1124
|
+
]
|
|
1095
1125
|
.join('\n')
|
|
1096
1126
|
.trimEnd() + '\n';
|
|
1097
1127
|
}
|