@murumets-ee/auth 0.1.0 → 0.1.1

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/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var F=Object.create;var p=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var $=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var k=(t,n)=>()=>(t&&(n=t(t=0)),n);var v=(t,n)=>{for(var e in n)p(t,e,{get:n[e],enumerable:!0})},C=(t,n,e,s)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of K(n))!J.call(t,r)&&r!==e&&p(t,r,{get:()=>n[r],enumerable:!(s=G(n,r))||s.enumerable});return t};var Q=(t,n,e)=>(e=t!=null?F($(t)):{},C(n||!t||!t.__esModule?p(e,"default",{value:t,enumerable:!0}):e,t)),V=t=>C(p({},"__esModule",{value:!0}),t);function N(){return{public:{},authenticated:{}}}function E(t){let n=new Map;for(let[e,s]of Object.entries(t)){let r=new Map;for(let[o,i]of Object.entries(s))r.set(o,new Set(i));n.set(e,r)}return(e,s,r)=>e==="admin"?!0:n.get(e)?.get(s)?.has(r)??!1}function I(t){return t.behaviors?.some(n=>n.name==="publishable")??!1}function O(t,n){let e={};for(let s of t)e[s.name]=I(s)?["view","create","update","delete","publish"]:["view","create","update","delete"];if(n)for(let s of n)s.resource&&s.actions&&!(s.resource in e)&&(e[s.resource]=[...s.actions]);return e}function f(t){let n={...y};for(let e of t)n[e.name]=I(e)?w:b;return n}function h(t,n){let e={},s={};e.user=[...y.user],e.session=[...y.session],s.user=[],s.session=[];for(let r of n)e[r.name]=I(r)?["view","create","update","delete","publish"]:["view","create","update","delete"],s[r.name]=["view"];return{admin:t.newRole(e),authenticated:t.newRole(s)}}var A,b,w,P,x,y,R=k(()=>{"use strict";A=require("better-auth/plugins/access"),b=["view","create","update","delete"],w=["view","create","update","delete","publish"],P=["admin","public","authenticated"],x={GET:"view",POST:"create",PATCH:"update",DELETE:"delete"};y={user:["create","list","set-role","ban","impersonate","delete","set-password","get","update"],session:["list","revoke","delete"]}});var j={};v(j,{createAuthServer:()=>ie});function m(t){let n=t?.context?.session,e=n?.user;return{id:e?.id??n?.userId,name:e?.name}}function Y(t,n){function e(r){n?.log(r).catch(()=>{})}let s=null;return{user:{create:{after:async r=>{try{await t.readWrite.execute(T.sql`UPDATE "user" SET role = 'admin' WHERE id = ${r.id} AND (SELECT COUNT(*) FROM "user") = 1`)}catch{}e({action:"auth.signup",entityType:"user",entityId:r.id,userId:r.id,userName:r.name,changes:{fields:{name:r.name,email:r.email}}})}},update:{before:async r=>{let o={};for(let[i,a]of Object.entries(r))X.has(i)||i==="id"||(o[i]=a);s=Object.keys(o).length>0?o:null},after:async(r,o)=>{let i=m(o),a=s;s=null,e({action:"auth.user.update",entityType:"user",entityId:r.id,userId:i.id,userName:i.name,changes:a?{fields:a}:void 0,metadata:{targetUser:r.name}})}},delete:{after:async(r,o)=>{let i=m(o);e({action:"auth.user.delete",entityType:"user",entityId:r.id,userId:i.id,userName:i.name,metadata:{targetUser:r.name}})}}}}}function re(t){function n(e){t.log(e).catch(()=>{})}return{after:(0,l.createAuthMiddleware)(async e=>{if(Z.some(c=>e.path.startsWith(c))){let u=e.context.returned?.status;if(!u)return;if(u>=400){let d=e.body?.email;n({action:"auth.login.failed",metadata:{...typeof d=="string"?{email:d}:{},status:u,path:e.path}})}else{let d=e.context.newSession;d?.user?.id&&n({action:"auth.login",entityType:"user",entityId:d.user.id,userId:d.user.id,userName:d.user.name})}return}if(e.path===ee){let c=e.context.returned;if(!c?.status||c.status<400){let u=m(e);u.id&&n({action:"auth.logout",entityType:"user",entityId:u.id,userId:u.id,userName:u.name})}return}let r=te[e.path];if(r){let c=e.context.returned;if(!c?.status||c.status<400){let u=m(e),d=e.body;n({action:r,entityType:"user",userId:u.id,userName:u.name,metadata:{...typeof d?.email=="string"?{email:d.email}:{},path:e.path}})}return}let o=ne[e.path];if(!o)return;let i=e.context.returned;if(i?.status&&i.status>=400)return;let a=m(e),g=e.body;n({action:o,entityType:"user",entityId:g?.userId??void 0,userId:a.id,userName:a.name,metadata:{targetUserId:g?.userId,...g?.role?{role:g.role}:{},path:e.path}})})}}function se(t){return(0,l.createAuthMiddleware)(async n=>{if(n.path!=="/sign-up/email")return;let e=await t.readWrite.execute(T.sql`SELECT COUNT(*)::text as count FROM "user"`);if(Number(e[0]?.count)>0)throw new l.APIError("FORBIDDEN",{message:"Sign-up is closed"})})}function ie(t,n,e){let s=[...n.entities.values()],r=f(s),o=(0,M.createAccessControl)(r),i=h(o,s),a={};return t.social?.google&&(a.google=t.social.google),t.social?.github&&(a.github=t.social.github),(0,D.betterAuth)({database:(0,L.drizzleAdapter)(n.db.readWrite,{provider:"pg",...t.schema?{schema:t.schema}:{}}),emailAndPassword:{enabled:t.providers?.includes("email")??!0},socialProviders:a,session:{expiresIn:t.session?.expiresIn??3600*2,updateAge:t.session?.updateAge??3600},rateLimit:{enabled:!0,window:60,max:100,storage:"memory",customRules:{"/sign-in/email":{window:60,max:5},"/sign-in/social":{window:60,max:10},"/sign-up/email":{window:60,max:3},"/forget-password":{window:60,max:3},"/reset-password":{window:60,max:5},"/admin/*":{window:60,max:20}}},databaseHooks:Y(n.db,e),hooks:{before:se(n.db),...e?re(e):{}},plugins:[(0,U.admin)({ac:o,roles:i,defaultRole:"authenticated"}),...t.organizations?[(0,W.organization)({ac:o,roles:i})]:[],...t.betterAuthPlugins??[],(0,H.nextCookies)()]})}var D,L,l,H,U,M,W,T,X,Z,ee,te,ne,q=k(()=>{"use strict";D=require("better-auth"),L=require("better-auth/adapters/drizzle"),l=require("better-auth/api"),H=require("better-auth/next-js"),U=require("better-auth/plugins"),M=require("better-auth/plugins/access"),W=require("better-auth/plugins/organization"),T=require("drizzle-orm");R();X=new Set(["updatedAt","createdAt"]);Z=["/sign-in/email","/sign-in/social"],ee="/sign-out",te={"/change-password":"auth.password.change","/forget-password":"auth.password.reset_request","/reset-password":"auth.password.reset"},ne={"/admin/set-role":"auth.admin.set_role","/admin/ban-user":"auth.admin.ban","/admin/unban-user":"auth.admin.unban","/admin/create-user":"auth.admin.create_user","/admin/remove-user":"auth.admin.remove_user","/admin/impersonate-user":"auth.admin.impersonate","/admin/stop-impersonating":"auth.admin.stop_impersonating","/admin/revoke-session":"auth.admin.revoke_session","/admin/revoke-sessions":"auth.admin.revoke_sessions"}});var oe={};v(oe,{ACTIONS:()=>b,ACTIONS_WITH_PUBLISH:()=>w,BUILT_IN_ROLES:()=>P,METHOD_TO_ACTION:()=>x,auth:()=>B,buildDefaultRoles:()=>h,buildInitialRoleDefinitions:()=>N,buildPermissionChecker:()=>E,buildResourceCatalog:()=>O,buildStatements:()=>f,createAccessControl:()=>A.createAccessControl,getAuth:()=>z,resolveAuthContext:()=>_});module.exports=V(oe);var le=require("server-only");async function _(t,n){let e=await t.api.getSession({headers:n});return e?{user:{id:e.user.id,groups:[e.user.role??"viewer"],name:e.user.name??void 0,email:e.user.email??void 0},requestId:crypto.randomUUID()}:{requestId:crypto.randomUUID()}}R();var S=null;function z(){if(!S)throw new Error("@murumets-ee/auth not initialized. Add auth() to your toolkit config plugins.");return S}function B(t={}){return{name:"@murumets-ee/auth",init:async n=>{let{createAuthServer:e}=await Promise.resolve().then(()=>(q(),j)),s;if(t.audit!==!1){let{createAuditLogger:r,createAuditDbWriter:o,createLogger:i}=await import("@murumets-ee/logging");s=r({logger:i({name:"auth-audit"}),dbWriter:o(n.db.readWrite)})}S=e(t,n,s),n.logger.info("Auth plugin initialized")}}}0&&(module.exports={ACTIONS,ACTIONS_WITH_PUBLISH,BUILT_IN_ROLES,METHOD_TO_ACTION,auth,buildDefaultRoles,buildInitialRoleDefinitions,buildPermissionChecker,buildResourceCatalog,buildStatements,createAccessControl,getAuth,resolveAuthContext});
@@ -0,0 +1,114 @@
1
+ import { RequestContext, PermissionChecker } from '@murumets-ee/core';
2
+ import { A as Auth } from './plugin-C-MA3A5U.cjs';
3
+ export { a as AuthConfig, b as auth, g as getAuth } from './plugin-C-MA3A5U.cjs';
4
+ import * as better_auth_plugins from 'better-auth/plugins';
5
+ import { Entity } from '@murumets-ee/entity';
6
+ import { createAccessControl } from 'better-auth/plugins/access';
7
+ export { createAccessControl } from 'better-auth/plugins/access';
8
+ import 'better-auth';
9
+ import '@murumets-ee/logging';
10
+
11
+ /**
12
+ * Bridge between better-auth sessions and the toolkit's RequestContext.
13
+ *
14
+ * This is the critical integration point: it resolves a better-auth session
15
+ * from request headers and returns a toolkit RequestContext that the entity
16
+ * system's AdminClient, QueryClient, and auditable behavior can read.
17
+ */
18
+
19
+ /**
20
+ * Resolve a better-auth session into a toolkit RequestContext.
21
+ *
22
+ * Call this from your Next.js middleware or server component to populate
23
+ * the toolkit's AsyncLocalStorage context.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // middleware.ts (user writes this — documented copy-paste)
28
+ * import { getAuth, resolveAuthContext } from '@murumets-ee/auth'
29
+ * import { runWithContext } from '@murumets-ee/core'
30
+ *
31
+ * export async function middleware(request: NextRequest) {
32
+ * const auth = getAuth()
33
+ * const ctx = await resolveAuthContext(auth, request.headers)
34
+ * return runWithContext(ctx, () => NextResponse.next())
35
+ * }
36
+ * ```
37
+ */
38
+ declare function resolveAuthContext(auth: Auth, headers: Headers): Promise<RequestContext>;
39
+
40
+ declare const ACTIONS: readonly ["view", "create", "update", "delete"];
41
+ declare const ACTIONS_WITH_PUBLISH: readonly ["view", "create", "update", "delete", "publish"];
42
+
43
+ /** Built-in role names — cannot be deleted via the roles editor. */
44
+ declare const BUILT_IN_ROLES: readonly ["admin", "public", "authenticated"];
45
+ /** Maps HTTP methods to permission action names. */
46
+ declare const METHOD_TO_ACTION: Record<string, string>;
47
+ /**
48
+ * Build initial role definitions for first run (no settings saved yet).
49
+ *
50
+ * All built-in non-admin roles start with ZERO permissions.
51
+ * Admin is never stored — it's hardcoded in the checker.
52
+ */
53
+ declare function buildInitialRoleDefinitions(): Record<string, Record<string, string[]>>;
54
+ /**
55
+ * Build a synchronous permission checker from role definitions.
56
+ *
57
+ * Rules:
58
+ * - `admin` role: ALWAYS returns `true` (hardcoded safety net, ignores settings)
59
+ * - All other roles: exact match from `roleDefinitions` (deny-by-default)
60
+ * - Unknown role / unknown resource / unknown action → `false`
61
+ */
62
+ declare function buildPermissionChecker(roleDefinitions: Record<string, Record<string, string[]>>): PermissionChecker;
63
+ /**
64
+ * Build a complete resource catalog from entities and admin routes.
65
+ *
66
+ * Used by:
67
+ * - `permissionRoutes()` config (`getStatements` callback)
68
+ * - Server-side permission page data loaders
69
+ *
70
+ * Entities automatically get CRUD actions. Publishable entities also get
71
+ * the `publish` action, which gates who can set status to 'published'.
72
+ * Routes with `resource` and `actions` are added if not already present.
73
+ */
74
+ declare function buildResourceCatalog(entities: {
75
+ name: string;
76
+ behaviors?: {
77
+ name: string;
78
+ }[];
79
+ }[], routes?: {
80
+ resource?: string;
81
+ actions?: readonly string[];
82
+ }[]): Record<string, string[]>;
83
+ /**
84
+ * Build a permission statement object from all registered entities,
85
+ * plus the admin plugin's built-in user/session resources.
86
+ *
87
+ * Publishable entities get the additional `publish` action.
88
+ * Result shape: `{ user: [...], session: [...], article: ['view', ...], category: [...] }`
89
+ */
90
+ declare function buildStatements(entities: Entity[]): Record<string, readonly string[]>;
91
+ /**
92
+ * Build the default toolkit roles for better-auth's access control.
93
+ *
94
+ * - **admin**: full CRUD on all entities + user/session management
95
+ * - **authenticated**: view only (better-auth's `defaultRole`)
96
+ */
97
+ declare function buildDefaultRoles(ac: ReturnType<typeof createAccessControl>, entities: Entity[]): {
98
+ admin: {
99
+ authorize<K_1 extends string>(request: K_1 extends infer T extends K ? { [key in T]?: better_auth_plugins.Subset<string, better_auth_plugins.Statements>[key] | {
100
+ actions: better_auth_plugins.Subset<string, better_auth_plugins.Statements>[key];
101
+ connector: "OR" | "AND";
102
+ } | undefined; } : never, connector?: "OR" | "AND"): better_auth_plugins.AuthorizeResponse;
103
+ statements: better_auth_plugins.Subset<string, better_auth_plugins.Statements>;
104
+ };
105
+ authenticated: {
106
+ authorize<K_1 extends string>(request: K_1 extends infer T extends K ? { [key in T]?: better_auth_plugins.Subset<string, better_auth_plugins.Statements>[key] | {
107
+ actions: better_auth_plugins.Subset<string, better_auth_plugins.Statements>[key];
108
+ connector: "OR" | "AND";
109
+ } | undefined; } : never, connector?: "OR" | "AND"): better_auth_plugins.AuthorizeResponse;
110
+ statements: better_auth_plugins.Subset<string, better_auth_plugins.Statements>;
111
+ };
112
+ };
113
+
114
+ export { ACTIONS, ACTIONS_WITH_PUBLISH, Auth, BUILT_IN_ROLES, METHOD_TO_ACTION, buildDefaultRoles, buildInitialRoleDefinitions, buildPermissionChecker, buildResourceCatalog, buildStatements, resolveAuthContext };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{a as f,b as c}from"./chunk-2GGGFSMD.js";import{a as o,b as s,c as u,d as i,e as n,f as m,g as a,h as p,i as d,j as l}from"./chunk-NH6AU5Z6.js";import"server-only";async function I(t,r){let e=await t.api.getSession({headers:r});return e?{user:{id:e.user.id,groups:[e.user.role??"viewer"],name:e.user.name??void 0,email:e.user.email??void 0},requestId:crypto.randomUUID()}:{requestId:crypto.randomUUID()}}export{s as ACTIONS,u as ACTIONS_WITH_PUBLISH,i as BUILT_IN_ROLES,n as METHOD_TO_ACTION,c as auth,l as buildDefaultRoles,m as buildInitialRoleDefinitions,a as buildPermissionChecker,p as buildResourceCatalog,d as buildStatements,o as createAccessControl,f as getAuth,I as resolveAuthContext};
1
+ import{a as f,b as c}from"./chunk-CJL274PJ.js";import{a as o,b as s,c as u,d as i,e as n,f as m,g as a,h as p,i as d,j as l}from"./chunk-NH6AU5Z6.js";import"server-only";async function I(t,r){let e=await t.api.getSession({headers:r});return e?{user:{id:e.user.id,groups:[e.user.role??"viewer"],name:e.user.name??void 0,email:e.user.email??void 0},requestId:crypto.randomUUID()}:{requestId:crypto.randomUUID()}}export{s as ACTIONS,u as ACTIONS_WITH_PUBLISH,i as BUILT_IN_ROLES,n as METHOD_TO_ACTION,c as auth,l as buildDefaultRoles,m as buildInitialRoleDefinitions,a as buildPermissionChecker,p as buildResourceCatalog,d as buildStatements,o as createAccessControl,f as getAuth,I as resolveAuthContext};