@albirex/platformatic-logto 1.2.5 → 1.3.2

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/lib/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ var Z=Object.create;var q=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var Q=Object.getPrototypeOf,V=Object.prototype.hasOwnProperty;var x=(e,t)=>{for(var o in t)q(e,o,{get:t[o],enumerable:!0})},_=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of J(t))!V.call(e,r)&&r!==o&&q(e,r,{get:()=>t[r],enumerable:!(n=Y(t,r))||n.enumerable});return e};var O=(e,t,o)=>(o=e!=null?Z(Q(e)):{},_(t||!e||!e.__esModule?q(o,"default",{value:e,enumerable:!0}):o,e)),ee=e=>_(q({},"__esModule",{value:!0}),e);var ie={};x(ie,{default:()=>re});module.exports=ee(ie);var G=O(require("fastify-plugin"),1),W=O(require("leven"),1),oe=O(require("fastify-user"),1);function te(e,t){let o=null;for(let n of e){for(let r of t)if(n.role===r){o=n;break}if(o)break}return o}var $=te;function A(e){if(e&&!e.reply)throw new Error("Missing reply in context. You should call this function with { ctx: { reply }}");return e.reply.request}function D(e,t,o,n=!1){let r=[],c=e.user;if(!c)return r.push(o),r;let a;if(n){let h=t.split(".");a=c;for(let F of h)a=a[F]}else a=c[t];return typeof a=="string"?r=a.split(","):Array.isArray(a)&&(r=a),r.length===0&&r.push(o),r}var T=O(require("@fastify/error"),1),S="PLT_DB_AUTH",b=(0,T.default)(`${S}_UNAUTHORIZED`,"operation not allowed",401),C=(0,T.default)(`${S}_FIELD_UNAUTHORIZED`,"field not allowed: %s",401),z=(0,T.default)(`${S}_NOT_NULLABLE_MISSING`,'missing not nullable field: "%s" in save rule for entity "%s"');var X=O(require("@albirex/fastify-logto"),1),N="platformatic-admin";async function se(e,t){e.register(X.default,{endpoint:t.logtoBaseUrl||"https://auth.example.com",appId:t.logtoAppId||"your-app-id",appSecret:t.logtoAppSecret||"your-app-secret"}),await e.register(oe,t.jwtPlugin);let o=t.adminSecret,n=t.rolePath||t.roleKey||"X-PLATFORMATIC-ROLE",r=t.userPath||t.userKey||"X-PLATFORMATIC-USER-ID",c=!!t.rolePath,a=t.anonymousRole||"anonymous";async function h(){let R=await e.logto.callAPI("/api/roles?type=User","GET");if(!R.ok)throw R;let P=await R.json(),f=[{role:a,entities:Object.keys(e.platformatic.entities),find:t.allowAnonymous,save:t.allowAnonymous,delete:t.allowAnonymous}];for(let l of P){let d=await e.logto.callAPI(`/api/roles/${l.id}/scopes`,"GET");if(!d.ok)throw d;let w=await d.json();for(let I of w){let g=l.name,[i,s]=I.name.split(":");if(!e.platformatic.entities[s]){let y=L(s);e.log.warn(`Unknown entity '${s}' in authorization rule. Did you mean '${y.entity}'?`);continue}let m=f.find(y=>y.role===g&&y.entity===s);if(m)t.checks?m[i]={checks:{userId:r}}:m[i]=!0;else{let y={role:g,entity:s};t.checks?y[i]={checks:{userId:r}}:y[i]=!0,t.defaults&&(y.defaults={userId:r}),f.push(y)}}}return e.log.debug("LogTo calculated rules"),e.log.debug(f),f}e.decorateRequest("setupDBAuthorizationUser",F);async function F(){await this.extractUser();let R=!1;o&&this.headers["x-platformatic-admin-secret"]===o&&(t.jwtPlugin.jwt?R=!0:(this.log.info("admin secret is valid"),this.user=new Proxy(this.headers,{get:(P,f)=>{let l;if(P[f.toString()])l=P[f.toString()];else{let d=f.toString().toLowerCase();l=P[d]}return!l&&f.toString().toLowerCase()===n.toLowerCase()&&(l=N),l}}))),R&&(this.user={[n]:N})}function L(R){return Object.keys(e.platformatic.entities).reduce((l,d)=>{let w=(0,W.default)(R,d);return w<l.distance&&(l.distance=w,l.entity=d),l},{distance:1/0,entity:null})}e.addHook("onReady",async function(){let R=await h(),P={};for(let f=0;f<R.length;f++){let l=R[f],d=null;if(l.entity)d=[l.entity];else if(l.entities)d=[...l.entities];else throw new Error(`Missing entity in authorization rule ${f}`);for(let w of d){let I={...l,entity:w,entities:void 0};if(!e.platformatic.entities[I.entity]){let g=L(w);throw new Error(`Unknown entity '${w}' in authorization rule ${f}. Did you mean '${g.entity}'?`)}P[w]||(P[w]=[]),P[w].push(I)}}for(let f of Object.keys(e.platformatic.entities)){let l=P[f]||[],d=e.platformatic.entities[f];if(void 0&&!1)throw new Error(`Subscription for entity "${f}" have conflictling rules across roles`);o&&l.push({role:N,find:!0,save:!0,delete:!0}),ne(d,l),e.platformatic.addEntityHooks(f,{async find(g,{where:i,ctx:s,fields:m,...y}={}){let p=A(s),u=await v(s,l,n,a,c);return M(u.find,m||Object.keys(e.platformatic.entities[f].fields)),i=await K(s,u.find,i,p.user),g({...y,where:i,ctx:s,fields:m})},async save(g,{input:i,ctx:s,fields:m,...y}){let p=A(s),u=await v(s,l,n,a,c);if(!u.save)throw new b;if(M(u.save,m),H(u.save,i),u.defaults)for(let k of Object.keys(u.defaults)){let j=u.defaults[k];typeof j=="function"?i[k]=await j({user:p.user,ctx:s,input:i}):i[k]=p.user[j]}let U=i[d.primaryKey]!==void 0,E={};if(E[d.primaryKey]={eq:i[d.primaryKey]},U){let k=await K(s,u.save,E,p.user);if((await d.find({where:k,ctx:s,fields:m})).length===0)throw new b;return g({input:i,ctx:s,fields:m,...y})}return g({input:i,ctx:s,fields:m,...y})},async insert(g,{inputs:i,ctx:s,fields:m,...y}){let p=A(s),u=await v(s,l,n,a,c);if(!u.save)throw new b;if(M(u.save,m),H(u.save,i),u.defaults)for(let U of i)for(let E of Object.keys(u.defaults)){let k=u.defaults[E];typeof k=="function"?U[E]=await k({user:p.user,ctx:s,input:U}):U[E]=p.user[k]}return g({inputs:i,ctx:s,fields:m,...y})},async delete(g,{where:i,ctx:s,fields:m,...y}){let p=A(s),u=await v(s,l,n,a,c);return i=await K(s,u.delete,i,p.user),g({where:i,ctx:s,fields:m,...y})},async updateMany(g,{where:i,ctx:s,fields:m,...y}){let p=A(s),u=await v(s,l,n,a,c);return i=await K(s,u.updateMany,i,p.user),g({...y,where:i,ctx:s,fields:m})}})}})}async function K(e,t,o,n){if(!t)throw new b;let r=A(e);if(o=o||{},typeof t=="object"){let{checks:c}=t;if(c)for(let a of Object.keys(c)){let h=c[a];if(typeof h=="string")o[a]={eq:r.user[h]};else for(let F of Object.keys(h)){let L=h[F];o[a]={[F]:r.user[L]}}}}else typeof t=="function"&&(o=await t({user:n,ctx:e,where:o}));return o}async function v(e,t,o,n,r=!1){let c=A(e);await c.setupDBAuthorizationUser();let a=D(c,o,n,r),h=$(t,a);if(!h)throw e.reply.request.log.warn({roles:a,rules:t},"no rule for roles"),new b;return e.reply.request.log.trace({roles:a,rule:h},"found rule"),h}function M(e,t){if(!e)throw new b;let{fields:o}=e;if(o){for(let n of t)if(!o.includes(n))throw new C(n)}}var B=(e,t)=>{for(let o of e){let n=Object.keys(o);for(let r of n)if(!t.includes(r))throw new C(r)}};function H(e,t){let{fields:o}=e;o&&(Array.isArray(t)?B(t,o):B([t],o))}function ne(e,t){let o=Object.values(e.fields).filter(n=>!n.isNullable&&!n.primaryKey).map(({camelcase:n})=>n);for(let n of t){let{entity:r,save:c}=n;if(c&&c.fields){let a=c.fields;for(let h of o)if(!a.includes(h))throw new z(h,r)}}}var re=(0,G.default)(se);
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/find-rule.ts","../src/utils/utils.ts","../src/utils/errors.ts"],"sourcesContent":["import fp from 'fastify-plugin'\nimport leven from 'leven'\nimport * as fastifyUser from 'fastify-user'\n\nimport findRule from './utils/find-rule.js'\nimport { getRequestFromContext, getRoles } from './utils/utils.js'\nimport { Unauthorized, UnauthorizedField, MissingNotNullableError } from './utils/errors.js'\nimport fastifyLogto from '@albirex/fastify-logto';\nimport { FastifyInstance } from 'fastify'\nimport type { FastifyUserPluginOptions } from 'fastify-user';\nimport type { Entity, PlatformaticContext } from '@platformatic/sql-mapper'\n\nconst PLT_ADMIN_ROLE = 'platformatic-admin'\n\nexport type PlatformaticRule = {\n role: string;\n entity?: string;\n entities?: string[];\n defaults?: Record<string, string>;\n checks?: boolean;\n find?: boolean;\n save?: boolean;\n delete?: boolean;\n\n};\n\nexport type PlatformaticLogtoAuthOptions = {\n logtoBaseUrl?: string;\n logtoAppId?: string;\n logtoAppSecret?: string;\n adminSecret?: string;\n rolePath?: string;\n roleKey?: string;\n userPath?: string;\n userKey?: string;\n anonymousRole?: string;\n allowAnonymous?: boolean;\n checks?: boolean;\n defaults?: boolean;\n jwtPlugin: FastifyUserPluginOptions\n};\n\nasync function auth(app: FastifyInstance, opts: PlatformaticLogtoAuthOptions) {\n app.register(fastifyLogto, {\n endpoint: opts.logtoBaseUrl || 'https://auth.example.com',\n appId: opts.logtoAppId || 'your-app-id',\n appSecret: opts.logtoAppSecret || 'your-app-secret',\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await app.register(fastifyUser as any, opts.jwtPlugin);\n\n const adminSecret = opts.adminSecret\n const roleKey = opts.rolePath || opts.roleKey || 'X-PLATFORMATIC-ROLE'\n const userKey = opts.userPath || opts.userKey || 'X-PLATFORMATIC-USER-ID'\n const isRolePath = !!opts.rolePath // if `true` the role is intepreted as path like `user.role`\n const anonymousRole = opts.anonymousRole || 'anonymous'\n\n async function composeLogToRules() {\n const rolesResp = await app.logto.callAPI('/api/roles?type=User', 'GET');\n\n if (!rolesResp.ok) {\n throw rolesResp;\n }\n\n const roles = await rolesResp.json();\n const rules: PlatformaticRule[] = [{\n role: anonymousRole,\n entities: Object.keys(app.platformatic.entities),\n find: opts.allowAnonymous,\n save: opts.allowAnonymous,\n delete: opts.allowAnonymous,\n }];\n\n for (const role of roles) {\n const scopesResp = await app.logto.callAPI(`/api/roles/${role.id}/scopes`, 'GET');\n\n if (!scopesResp.ok) {\n throw scopesResp;\n }\n\n const scopes = await scopesResp.json();\n\n for (const scope of scopes) {\n const roleName = role.name;\n const [scopeAction, entity] = scope.name.split(':');\n\n if (!app.platformatic.entities[entity]) {\n const nearest = findNearestEntity(entity)\n app.log.warn(`Unknown entity '${entity}' in authorization rule. Did you mean '${nearest.entity}'?`)\n continue;\n }\n\n const checkExists = rules.find(r => r.role === roleName && r.entity === entity);\n if (checkExists) {\n if (opts.checks) {\n checkExists[scopeAction] = {\n checks: {\n userId: userKey\n }\n };\n } else {\n checkExists[scopeAction] = true;\n }\n } else {\n const newRule: PlatformaticRule = {\n role: roleName,\n entity,\n };\n\n if (opts.checks) {\n newRule[scopeAction] = {\n checks: {\n userId: userKey\n }\n };\n } else {\n newRule[scopeAction] = true;\n }\n\n if (opts.defaults) {\n newRule.defaults = {\n userId: userKey\n };\n }\n\n rules.push(newRule);\n }\n }\n }\n\n app.log.debug('LogTo calculated rules');\n app.log.debug(rules);\n return rules;\n }\n\n app.decorateRequest('setupDBAuthorizationUser', setupUser)\n\n async function setupUser() {\n // if (!adminSecret) {\n await this.extractUser()\n // }\n\n let forceAdminRole = false\n if (adminSecret && this.headers['x-platformatic-admin-secret'] === adminSecret) {\n if (opts.jwtPlugin.jwt) {\n forceAdminRole = true\n } else {\n this.log.info('admin secret is valid')\n this.user = new Proxy(this.headers, {\n get: (target, key) => {\n let value;\n if (!target[key.toString()]) {\n const newKey = key.toString().toLowerCase()\n value = target[newKey]\n } else {\n value = target[key.toString()]\n }\n\n if (!value && key.toString().toLowerCase() === roleKey.toLowerCase()) {\n value = PLT_ADMIN_ROLE\n }\n return value\n },\n })\n }\n }\n\n if (forceAdminRole) {\n // We replace just the role in `request.user`, all the rest is untouched\n this.user = {\n // ...request.user,\n [roleKey]: PLT_ADMIN_ROLE,\n }\n }\n }\n\n function findNearestEntity(ruleEntity) {\n // There is an unknown entity. Let's find out the nearest one for a nice error message\n const entities = Object.keys(app.platformatic.entities)\n\n const nearest = entities.reduce((acc, entity) => {\n const distance = leven(ruleEntity, entity)\n if (distance < acc.distance) {\n acc.distance = distance\n acc.entity = entity\n }\n return acc\n }, { distance: Infinity, entity: null })\n return nearest\n }\n\n app.addHook('onReady', async function () {\n const rules = await composeLogToRules();\n\n // TODO validate that there is at most a rule for a given role\n const entityRules = {};\n for (let i = 0; i < rules.length; i++) {\n const rule = rules[i]\n\n let ruleEntities = null\n if (rule.entity) {\n ruleEntities = [rule.entity]\n } else if (rule.entities) {\n ruleEntities = [...rule.entities]\n } else {\n throw new Error(`Missing entity in authorization rule ${i}`)\n }\n\n for (const ruleEntity of ruleEntities) {\n const newRule = { ...rule, entity: ruleEntity, entities: undefined }\n if (!app.platformatic.entities[newRule.entity]) {\n const nearest = findNearestEntity(ruleEntity)\n throw new Error(`Unknown entity '${ruleEntity}' in authorization rule ${i}. Did you mean '${nearest.entity}'?`)\n }\n\n if (!entityRules[ruleEntity]) {\n entityRules[ruleEntity] = []\n }\n entityRules[ruleEntity].push(newRule)\n }\n }\n\n for (const entityKey of Object.keys(app.platformatic.entities)) {\n const rules = entityRules[entityKey] || []\n const type = app.platformatic.entities[entityKey]\n\n // We have subscriptions!\n let userPropToFillForPublish\n const topicsWithoutChecks = false\n\n // mqtt\n // if (app.platformatic.mq) {\n // for (const rule of rules) {\n // const checks = rule.find?.checks\n // if (typeof checks !== 'object') {\n // topicsWithoutChecks = !!rule.find\n // continue\n // }\n // const keys = Object.keys(checks)\n // if (keys.length !== 1) {\n // throw new Error(`Subscription requires that the role \"${rule.role}\" has only one check in the find rule for entity \"${rule.entity}\"`)\n // }\n // const key = keys[0]\n\n // const val = typeof checks[key] === 'object' ? checks[key].eq : checks[key]\n // if (userPropToFillForPublish && userPropToFillForPublish.val !== val) {\n // throw new Error('Unable to configure subscriptions and authorization due to multiple check clauses in find')\n // }\n // userPropToFillForPublish = { key, val }\n // }\n // }\n\n if (userPropToFillForPublish && topicsWithoutChecks) {\n throw new Error(`Subscription for entity \"${entityKey}\" have conflictling rules across roles`)\n }\n\n // MUST set this after doing the security checks on the subscriptions\n if (adminSecret) {\n rules.push({\n role: PLT_ADMIN_ROLE,\n find: true,\n save: true,\n delete: true,\n })\n }\n\n // If we have `fields` in save rules, we need to check if all the not-nullable\n // fields are specified\n checkSaveMandatoryFieldsInRules(type, rules)\n\n // function useOriginal(skipAuth: boolean, ctx: PlatformaticContext) {\n // if (skipAuth === false && !ctx) {\n // throw new Error('Cannot set skipAuth to `false` without ctx')\n // }\n\n // return skipAuth || !ctx\n // }\n\n app.platformatic.addEntityHooks(entityKey, {\n async find(originalFind, { where, ctx, fields, ...restOpts } = {}) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalFind({ ...restOpts, where, ctx, fields })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n checkFieldsFromRule(rule.find, fields || Object.keys(app.platformatic.entities[entityKey].fields))\n where = await fromRuleToWhere(ctx, rule.find, where, request.user)\n\n return originalFind({ ...restOpts, where, ctx, fields })\n },\n async save(originalSave, { input, ctx, fields, ...restOpts }) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalSave({ ctx, input, fields, ...restOpts })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n\n if (!rule.save) {\n throw new Unauthorized()\n }\n checkFieldsFromRule(rule.save, fields)\n checkInputFromRuleFields(rule.save, input)\n\n if (rule.defaults) {\n for (const key of Object.keys(rule.defaults)) {\n const defaults = rule.defaults[key]\n if (typeof defaults === 'function') {\n input[key] = await defaults({ user: request.user, ctx, input })\n } else {\n input[key] = request.user[defaults]\n }\n }\n }\n\n const hasAllPrimaryKeys = input[type.primaryKey] !== undefined;\n const whereConditions = {}\n whereConditions[type.primaryKey] = { eq: input[type.primaryKey] }\n\n if (hasAllPrimaryKeys) {\n const where = await fromRuleToWhere(ctx, rule.save, whereConditions, request.user)\n\n const found = await type.find({\n where,\n ctx,\n fields,\n })\n\n if (found.length === 0) {\n throw new Unauthorized()\n }\n\n return originalSave({ input, ctx, fields, ...restOpts })\n }\n\n return originalSave({ input, ctx, fields, ...restOpts })\n },\n\n async insert(originalInsert, { inputs, ctx, fields, ...restOpts }) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalInsert({ inputs, ctx, fields, ...restOpts })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n\n if (!rule.save) {\n throw new Unauthorized()\n }\n\n checkFieldsFromRule(rule.save, fields)\n checkInputFromRuleFields(rule.save, inputs)\n\n /* istanbul ignore else */\n if (rule.defaults) {\n for (const input of inputs) {\n for (const key of Object.keys(rule.defaults)) {\n const defaults = rule.defaults[key]\n if (typeof defaults === 'function') {\n input[key] = await defaults({ user: request.user, ctx, input })\n } else {\n input[key] = request.user[defaults]\n }\n }\n }\n }\n\n return originalInsert({ inputs, ctx, fields, ...restOpts })\n },\n\n async delete(originalDelete, { where, ctx, fields, ...restOpts }) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalDelete({ where, ctx, fields, ...restOpts })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n\n where = await fromRuleToWhere(ctx, rule.delete, where, request.user)\n\n return originalDelete({ where, ctx, fields, ...restOpts })\n },\n\n async updateMany(originalUpdateMany, { where, ctx, fields, ...restOpts }) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalUpdateMany({ ...restOpts, where, ctx, fields })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n\n where = await fromRuleToWhere(ctx, rule.updateMany, where, request.user)\n\n return originalUpdateMany({ ...restOpts, where, ctx, fields })\n },\n })\n }\n })\n}\n\nasync function fromRuleToWhere(ctx: PlatformaticContext, rule, where, user) {\n if (!rule) {\n throw new Unauthorized()\n }\n const request = getRequestFromContext(ctx)\n /* istanbul ignore next */\n where = where || {}\n\n if (typeof rule === 'object') {\n const { checks } = rule\n\n /* istanbul ignore else */\n if (checks) {\n for (const key of Object.keys(checks)) {\n const clauses = checks[key]\n if (typeof clauses === 'string') {\n // case: \"userId\": \"X-PLATFORMATIC-USER-ID\"\n where[key] = {\n eq: request.user[clauses],\n }\n } else {\n // case:\n // userId: {\n // eq: 'X-PLATFORMATIC-USER-ID'\n // }\n for (const clauseKey of Object.keys(clauses)) {\n const clause = clauses[clauseKey]\n where[key] = {\n [clauseKey]: request.user[clause],\n }\n }\n }\n }\n }\n } else if (typeof rule === 'function') {\n where = await rule({ user, ctx, where })\n }\n return where\n}\n\nasync function findRuleForRequestUser(ctx: PlatformaticContext, rules: PlatformaticRule[], roleKey: string, anonymousRole: string, isRolePath = false) {\n const request = getRequestFromContext(ctx)\n await request.setupDBAuthorizationUser()\n const roles = getRoles(request, roleKey, anonymousRole, isRolePath)\n const rule = findRule(rules, roles)\n if (!rule) {\n ctx.reply.request.log.warn({ roles, rules }, 'no rule for roles')\n throw new Unauthorized()\n }\n ctx.reply.request.log.trace({ roles, rule }, 'found rule')\n return rule\n}\n\nfunction checkFieldsFromRule(rule, fields) {\n if (!rule) {\n throw new Unauthorized()\n }\n const { fields: fieldsFromRule } = rule\n /* istanbul ignore else */\n if (fieldsFromRule) {\n for (const field of fields) {\n if (!fieldsFromRule.includes(field)) {\n throw new UnauthorizedField(field)\n }\n }\n }\n}\n\nconst validateInputs = (inputs, fieldsFromRule) => {\n for (const input of inputs) {\n const inputFields = Object.keys(input)\n for (const inputField of inputFields) {\n if (!fieldsFromRule.includes(inputField)) {\n throw new UnauthorizedField(inputField)\n }\n }\n }\n}\n\nfunction checkInputFromRuleFields(rule, inputs) {\n const { fields: fieldsFromRule } = rule\n /* istanbul ignore else */\n if (fieldsFromRule) {\n if (!Array.isArray(inputs)) {\n // save\n validateInputs([inputs], fieldsFromRule)\n } else {\n // insert\n validateInputs(inputs, fieldsFromRule)\n }\n }\n}\n\nfunction checkSaveMandatoryFieldsInRules(type: Entity, rules) {\n // List of not nullable, not PKs field to validate save/insert when allowed fields are specified on the rule\n const mandatoryFields =\n Object.values(type.fields)\n .filter(k => (!k.isNullable && !k.primaryKey))\n .map(({ camelcase }) => (camelcase))\n\n for (const rule of rules) {\n const { entity, save } = rule\n if (save && save.fields) {\n const fields = save.fields\n for (const mField of mandatoryFields) {\n if (!fields.includes(mField)) {\n throw new MissingNotNullableError(mField, entity)\n }\n }\n }\n }\n}\n\nexport default fp(auth)\n","'use strict'\n\nimport { PlatformaticRule } from '../index.js'\n\nfunction findRule(rules: PlatformaticRule[], roles: string[]) {\n let found = null\n for (const rule of rules) {\n for (const role of roles) {\n if (rule.role === role) {\n found = rule\n break\n }\n }\n if (found) {\n break\n }\n }\n return found\n}\n\nexport default findRule\n","'use strict'\n\nexport function getRequestFromContext (ctx) {\n if (ctx && !ctx.reply) {\n throw new Error('Missing reply in context. You should call this function with { ctx: { reply }}')\n }\n return ctx.reply.request\n}\n\nexport function getRoles (request, roleKey, anonymousRole, isRolePath = false) {\n let output = []\n const user = request.user\n if (!user) {\n output.push(anonymousRole)\n return output\n }\n\n let rolesRaw\n if (isRolePath) {\n const roleKeys = roleKey.split('.')\n rolesRaw = user\n for (const key of roleKeys) {\n rolesRaw = rolesRaw[key]\n }\n } else {\n rolesRaw = user[roleKey]\n }\n\n if (typeof rolesRaw === 'string') {\n output = rolesRaw.split(',')\n } else if (Array.isArray(rolesRaw)) {\n output = rolesRaw\n }\n if (output.length === 0) {\n output.push(anonymousRole)\n }\n\n return output\n}\n","'use strict'\n\nimport createError from '@fastify/error'\n\nconst ERROR_PREFIX = 'PLT_DB_AUTH'\n\nexport const Unauthorized = createError(`${ERROR_PREFIX}_UNAUTHORIZED`, 'operation not allowed', 401)\nexport const UnauthorizedField = createError(`${ERROR_PREFIX}_FIELD_UNAUTHORIZED`, 'field not allowed: %s', 401)\nexport const MissingNotNullableError = createError(`${ERROR_PREFIX}_NOT_NULLABLE_MISSING`, 'missing not nullable field: \"%s\" in save rule for entity \"%s\"')\n"],"mappings":"8iBAAA,IAAAA,GAAA,GAAAC,EAAAD,GAAA,aAAAE,KAAA,eAAAC,GAAAH,IAAA,IAAAI,EAAe,+BACfC,EAAkB,sBAClBC,GAA6B,6BCE7B,SAASC,GAASC,EAA2BC,EAAiB,CAC5D,IAAIC,EAAQ,KACZ,QAAWC,KAAQH,EAAO,CACxB,QAAWI,KAAQH,EACjB,GAAIE,EAAK,OAASC,EAAM,CACtBF,EAAQC,EACR,KACF,CAEF,GAAID,EACF,KAEJ,CACA,OAAOA,CACT,CAEA,IAAOG,EAAQN,GClBR,SAASO,EAAuBC,EAAK,CAC1C,GAAIA,GAAO,CAACA,EAAI,MACd,MAAM,IAAI,MAAM,gFAAgF,EAElG,OAAOA,EAAI,MAAM,OACnB,CAEO,SAASC,EAAUC,EAASC,EAASC,EAAeC,EAAa,GAAO,CAC7E,IAAIC,EAAS,CAAC,EACRC,EAAOL,EAAQ,KACrB,GAAI,CAACK,EACH,OAAAD,EAAO,KAAKF,CAAa,EAClBE,EAGT,IAAIE,EACJ,GAAIH,EAAY,CACd,IAAMI,EAAWN,EAAQ,MAAM,GAAG,EAClCK,EAAWD,EACX,QAAWG,KAAOD,EAChBD,EAAWA,EAASE,CAAG,CAE3B,MACEF,EAAWD,EAAKJ,CAAO,EAGzB,OAAI,OAAOK,GAAa,SACtBF,EAASE,EAAS,MAAM,GAAG,EAClB,MAAM,QAAQA,CAAQ,IAC/BF,EAASE,GAEPF,EAAO,SAAW,GACpBA,EAAO,KAAKF,CAAa,EAGpBE,CACT,CCpCA,IAAAK,EAAwB,+BAElBC,EAAe,cAERC,KAAe,EAAAC,SAAY,GAAGF,CAAY,gBAAiB,wBAAyB,GAAG,EACvFG,KAAoB,EAAAD,SAAY,GAAGF,CAAY,sBAAuB,wBAAyB,GAAG,EAClGI,KAA0B,EAAAF,SAAY,GAAGF,CAAY,wBAAyB,+DAA+D,EHD1J,IAAAK,EAAyB,uCAKnBC,EAAiB,qBA8BvB,eAAeC,GAAKC,EAAsBC,EAAoC,CAC1ED,EAAI,SAAS,EAAAE,QAAc,CACvB,SAAUD,EAAK,cAAgB,2BAC/B,MAAOA,EAAK,YAAc,cAC1B,UAAWA,EAAK,gBAAkB,iBACtC,CAAC,EAED,MAAMD,EAAI,SAASG,GAAoBF,EAAK,SAAS,EAErD,IAAMG,EAAcH,EAAK,YACnBI,EAAUJ,EAAK,UAAYA,EAAK,SAAW,sBAC3CK,EAAUL,EAAK,UAAYA,EAAK,SAAW,yBAC3CM,EAAa,CAAC,CAACN,EAAK,SACpBO,EAAgBP,EAAK,eAAiB,YAE5C,eAAeQ,GAAoB,CAC/B,IAAMC,EAAY,MAAMV,EAAI,MAAM,QAAQ,uBAAwB,KAAK,EAEvE,GAAI,CAACU,EAAU,GACX,MAAMA,EAGV,IAAMC,EAAQ,MAAMD,EAAU,KAAK,EAC7BE,EAA4B,CAAC,CAC/B,KAAMJ,EACN,SAAU,OAAO,KAAKR,EAAI,aAAa,QAAQ,EAC/C,KAAMC,EAAK,eACX,KAAMA,EAAK,eACX,OAAQA,EAAK,cACjB,CAAC,EAED,QAAWY,KAAQF,EAAO,CACtB,IAAMG,EAAa,MAAMd,EAAI,MAAM,QAAQ,cAAca,EAAK,EAAE,UAAW,KAAK,EAEhF,GAAI,CAACC,EAAW,GACZ,MAAMA,EAGV,IAAMC,EAAS,MAAMD,EAAW,KAAK,EAErC,QAAWE,KAASD,EAAQ,CACxB,IAAME,EAAWJ,EAAK,KAChB,CAACK,EAAaC,CAAM,EAAIH,EAAM,KAAK,MAAM,GAAG,EAElD,GAAI,CAAChB,EAAI,aAAa,SAASmB,CAAM,EAAG,CACpC,IAAMC,EAAUC,EAAkBF,CAAM,EACxCnB,EAAI,IAAI,KAAK,mBAAmBmB,CAAM,0CAA0CC,EAAQ,MAAM,IAAI,EAClG,QACJ,CAEA,IAAME,EAAcV,EAAM,KAAKW,GAAKA,EAAE,OAASN,GAAYM,EAAE,SAAWJ,CAAM,EAC9E,GAAIG,EACIrB,EAAK,OACLqB,EAAYJ,CAAW,EAAI,CACvB,OAAQ,CACJ,OAAQZ,CACZ,CACJ,EAEAgB,EAAYJ,CAAW,EAAI,OAE5B,CACH,IAAMM,EAA4B,CAC9B,KAAMP,EACN,OAAAE,CACJ,EAEIlB,EAAK,OACLuB,EAAQN,CAAW,EAAI,CACnB,OAAQ,CACJ,OAAQZ,CACZ,CACJ,EAEAkB,EAAQN,CAAW,EAAI,GAGvBjB,EAAK,WACLuB,EAAQ,SAAW,CACf,OAAQlB,CACZ,GAGJM,EAAM,KAAKY,CAAO,CACtB,CACJ,CACJ,CAEA,OAAAxB,EAAI,IAAI,MAAM,wBAAwB,EACtCA,EAAI,IAAI,MAAMY,CAAK,EACZA,CACX,CAEAZ,EAAI,gBAAgB,2BAA4ByB,CAAS,EAEzD,eAAeA,GAAY,CAEvB,MAAM,KAAK,YAAY,EAGvB,IAAIC,EAAiB,GACjBtB,GAAe,KAAK,QAAQ,6BAA6B,IAAMA,IAC3DH,EAAK,UAAU,IACfyB,EAAiB,IAEjB,KAAK,IAAI,KAAK,uBAAuB,EACrC,KAAK,KAAO,IAAI,MAAM,KAAK,QAAS,CAChC,IAAK,CAACC,EAAQC,IAAQ,CAClB,IAAIC,EACJ,GAAKF,EAAOC,EAAI,SAAS,CAAC,EAItBC,EAAQF,EAAOC,EAAI,SAAS,CAAC,MAJJ,CACzB,IAAME,EAASF,EAAI,SAAS,EAAE,YAAY,EAC1CC,EAAQF,EAAOG,CAAM,CACzB,CAIA,MAAI,CAACD,GAASD,EAAI,SAAS,EAAE,YAAY,IAAMvB,EAAQ,YAAY,IAC/DwB,EAAQ/B,GAEL+B,CACX,CACJ,CAAC,IAILH,IAEA,KAAK,KAAO,CAER,CAACrB,CAAO,EAAGP,CACf,EAER,CAEA,SAASuB,EAAkBU,EAAY,CAYnC,OAViB,OAAO,KAAK/B,EAAI,aAAa,QAAQ,EAE7B,OAAO,CAACgC,EAAKb,IAAW,CAC7C,IAAMc,KAAW,EAAAC,SAAMH,EAAYZ,CAAM,EACzC,OAAIc,EAAWD,EAAI,WACfA,EAAI,SAAWC,EACfD,EAAI,OAASb,GAEVa,CACX,EAAG,CAAE,SAAU,IAAU,OAAQ,IAAK,CAAC,CAE3C,CAEAhC,EAAI,QAAQ,UAAW,gBAAkB,CACrC,IAAMY,EAAQ,MAAMH,EAAkB,EAGhC0B,EAAc,CAAC,EACrB,QAASC,EAAI,EAAGA,EAAIxB,EAAM,OAAQwB,IAAK,CACnC,IAAMC,EAAOzB,EAAMwB,CAAC,EAEhBE,EAAe,KACnB,GAAID,EAAK,OACLC,EAAe,CAACD,EAAK,MAAM,UACpBA,EAAK,SACZC,EAAe,CAAC,GAAGD,EAAK,QAAQ,MAEhC,OAAM,IAAI,MAAM,wCAAwCD,CAAC,EAAE,EAG/D,QAAWL,KAAcO,EAAc,CACnC,IAAMd,EAAU,CAAE,GAAGa,EAAM,OAAQN,EAAY,SAAU,MAAU,EACnE,GAAI,CAAC/B,EAAI,aAAa,SAASwB,EAAQ,MAAM,EAAG,CAC5C,IAAMJ,EAAUC,EAAkBU,CAAU,EAC5C,MAAM,IAAI,MAAM,mBAAmBA,CAAU,2BAA2BK,CAAC,mBAAmBhB,EAAQ,MAAM,IAAI,CAClH,CAEKe,EAAYJ,CAAU,IACvBI,EAAYJ,CAAU,EAAI,CAAC,GAE/BI,EAAYJ,CAAU,EAAE,KAAKP,CAAO,CACxC,CACJ,CAEA,QAAWe,KAAa,OAAO,KAAKvC,EAAI,aAAa,QAAQ,EAAG,CAC5D,IAAMY,EAAQuB,EAAYI,CAAS,GAAK,CAAC,EACnCC,EAAOxC,EAAI,aAAa,SAASuC,CAAS,EA4BhD,GAzBI,QACwB,GAyBxB,MAAM,IAAI,MAAM,4BAA4BA,CAAS,wCAAwC,EAI7FnC,GACAQ,EAAM,KAAK,CACP,KAAMd,EACN,KAAM,GACN,KAAM,GACN,OAAQ,EACZ,CAAC,EAKL2C,GAAgCD,EAAM5B,CAAK,EAU3CZ,EAAI,aAAa,eAAeuC,EAAW,CACvC,MAAM,KAAKG,EAAc,CAAE,MAAAC,EAAO,IAAAC,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAI,CAAC,EAAG,CAI/D,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EACxF,OAAA2C,EAAoBb,EAAK,KAAMQ,GAAU,OAAO,KAAK7C,EAAI,aAAa,SAASuC,CAAS,EAAE,MAAM,CAAC,EACjGI,EAAQ,MAAMQ,EAAgBP,EAAKP,EAAK,KAAMM,EAAOI,EAAQ,IAAI,EAE1DL,EAAa,CAAE,GAAGI,EAAU,MAAAH,EAAO,IAAAC,EAAK,OAAAC,CAAO,CAAC,CAC3D,EACA,MAAM,KAAKO,EAAc,CAAE,MAAAC,EAAO,IAAAT,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAG,CAI1D,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EAExF,GAAI,CAAC8B,EAAK,KACN,MAAM,IAAIiB,EAKd,GAHAJ,EAAoBb,EAAK,KAAMQ,CAAM,EACrCU,EAAyBlB,EAAK,KAAMgB,CAAK,EAErChB,EAAK,SACL,QAAWT,KAAO,OAAO,KAAKS,EAAK,QAAQ,EAAG,CAC1C,IAAMmB,EAAWnB,EAAK,SAAST,CAAG,EAC9B,OAAO4B,GAAa,WACpBH,EAAMzB,CAAG,EAAI,MAAM4B,EAAS,CAAE,KAAMT,EAAQ,KAAM,IAAAH,EAAK,MAAAS,CAAM,CAAC,EAE9DA,EAAMzB,CAAG,EAAImB,EAAQ,KAAKS,CAAQ,CAE1C,CAGJ,IAAMC,EAAoBJ,EAAMb,EAAK,UAAU,IAAM,OAC/CkB,EAAkB,CAAC,EAGzB,GAFAA,EAAgBlB,EAAK,UAAU,EAAI,CAAE,GAAIa,EAAMb,EAAK,UAAU,CAAE,EAE5DiB,EAAmB,CACnB,IAAMd,EAAQ,MAAMQ,EAAgBP,EAAKP,EAAK,KAAMqB,EAAiBX,EAAQ,IAAI,EAQjF,IANc,MAAMP,EAAK,KAAK,CAC1B,MAAAG,EACA,IAAAC,EACA,OAAAC,CACJ,CAAC,GAES,SAAW,EACjB,MAAM,IAAIS,EAGd,OAAOF,EAAa,CAAE,MAAAC,EAAO,IAAAT,EAAK,OAAAC,EAAQ,GAAGC,CAAS,CAAC,CAC3D,CAEA,OAAOM,EAAa,CAAE,MAAAC,EAAO,IAAAT,EAAK,OAAAC,EAAQ,GAAGC,CAAS,CAAC,CAC3D,EAEA,MAAM,OAAOa,EAAgB,CAAE,OAAAC,EAAQ,IAAAhB,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAG,CAI/D,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EAExF,GAAI,CAAC8B,EAAK,KACN,MAAM,IAAIiB,EAOd,GAJAJ,EAAoBb,EAAK,KAAMQ,CAAM,EACrCU,EAAyBlB,EAAK,KAAMuB,CAAM,EAGtCvB,EAAK,SACL,QAAWgB,KAASO,EAChB,QAAWhC,KAAO,OAAO,KAAKS,EAAK,QAAQ,EAAG,CAC1C,IAAMmB,EAAWnB,EAAK,SAAST,CAAG,EAC9B,OAAO4B,GAAa,WACpBH,EAAMzB,CAAG,EAAI,MAAM4B,EAAS,CAAE,KAAMT,EAAQ,KAAM,IAAAH,EAAK,MAAAS,CAAM,CAAC,EAE9DA,EAAMzB,CAAG,EAAImB,EAAQ,KAAKS,CAAQ,CAE1C,CAIR,OAAOG,EAAe,CAAE,OAAAC,EAAQ,IAAAhB,EAAK,OAAAC,EAAQ,GAAGC,CAAS,CAAC,CAC9D,EAEA,MAAM,OAAOe,EAAgB,CAAE,MAAAlB,EAAO,IAAAC,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAG,CAI9D,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EAExF,OAAAoC,EAAQ,MAAMQ,EAAgBP,EAAKP,EAAK,OAAQM,EAAOI,EAAQ,IAAI,EAE5Dc,EAAe,CAAE,MAAAlB,EAAO,IAAAC,EAAK,OAAAC,EAAQ,GAAGC,CAAS,CAAC,CAC7D,EAEA,MAAM,WAAWgB,EAAoB,CAAE,MAAAnB,EAAO,IAAAC,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAG,CAItE,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EAExF,OAAAoC,EAAQ,MAAMQ,EAAgBP,EAAKP,EAAK,WAAYM,EAAOI,EAAQ,IAAI,EAEhEe,EAAmB,CAAE,GAAGhB,EAAU,MAAAH,EAAO,IAAAC,EAAK,OAAAC,CAAO,CAAC,CACjE,CACJ,CAAC,CACL,CACJ,CAAC,CACL,CAEA,eAAeM,EAAgBP,EAA0BP,EAAMM,EAAOoB,EAAM,CACxE,GAAI,CAAC1B,EACD,MAAM,IAAIiB,EAEd,IAAMP,EAAUC,EAAsBJ,CAAG,EAIzC,GAFAD,EAAQA,GAAS,CAAC,EAEd,OAAON,GAAS,SAAU,CAC1B,GAAM,CAAE,OAAA2B,CAAO,EAAI3B,EAGnB,GAAI2B,EACA,QAAWpC,KAAO,OAAO,KAAKoC,CAAM,EAAG,CACnC,IAAMC,EAAUD,EAAOpC,CAAG,EAC1B,GAAI,OAAOqC,GAAY,SAEnBtB,EAAMf,CAAG,EAAI,CACT,GAAImB,EAAQ,KAAKkB,CAAO,CAC5B,MAMA,SAAWC,KAAa,OAAO,KAAKD,CAAO,EAAG,CAC1C,IAAME,EAASF,EAAQC,CAAS,EAChCvB,EAAMf,CAAG,EAAI,CACT,CAACsC,CAAS,EAAGnB,EAAQ,KAAKoB,CAAM,CACpC,CACJ,CAER,CAER,MAAW,OAAO9B,GAAS,aACvBM,EAAQ,MAAMN,EAAK,CAAE,KAAA0B,EAAM,IAAAnB,EAAK,MAAAD,CAAM,CAAC,GAE3C,OAAOA,CACX,CAEA,eAAeM,EAAuBL,EAA0BhC,EAA2BP,EAAiBG,EAAuBD,EAAa,GAAO,CACnJ,IAAMwC,EAAUC,EAAsBJ,CAAG,EACzC,MAAMG,EAAQ,yBAAyB,EACvC,IAAMpC,EAAQyD,EAASrB,EAAS1C,EAASG,EAAeD,CAAU,EAC5D8B,EAAOgC,EAASzD,EAAOD,CAAK,EAClC,GAAI,CAAC0B,EACD,MAAAO,EAAI,MAAM,QAAQ,IAAI,KAAK,CAAE,MAAAjC,EAAO,MAAAC,CAAM,EAAG,mBAAmB,EAC1D,IAAI0C,EAEd,OAAAV,EAAI,MAAM,QAAQ,IAAI,MAAM,CAAE,MAAAjC,EAAO,KAAA0B,CAAK,EAAG,YAAY,EAClDA,CACX,CAEA,SAASa,EAAoBb,EAAMQ,EAAQ,CACvC,GAAI,CAACR,EACD,MAAM,IAAIiB,EAEd,GAAM,CAAE,OAAQgB,CAAe,EAAIjC,EAEnC,GAAIiC,GACA,QAAWC,KAAS1B,EAChB,GAAI,CAACyB,EAAe,SAASC,CAAK,EAC9B,MAAM,IAAIC,EAAkBD,CAAK,EAIjD,CAEA,IAAME,EAAiB,CAACb,EAAQU,IAAmB,CAC/C,QAAWjB,KAASO,EAAQ,CACxB,IAAMc,EAAc,OAAO,KAAKrB,CAAK,EACrC,QAAWsB,KAAcD,EACrB,GAAI,CAACJ,EAAe,SAASK,CAAU,EACnC,MAAM,IAAIH,EAAkBG,CAAU,CAGlD,CACJ,EAEA,SAASpB,EAAyBlB,EAAMuB,EAAQ,CAC5C,GAAM,CAAE,OAAQU,CAAe,EAAIjC,EAE/BiC,IACK,MAAM,QAAQV,CAAM,EAKrBa,EAAeb,EAAQU,CAAc,EAHrCG,EAAe,CAACb,CAAM,EAAGU,CAAc,EAMnD,CAEA,SAAS7B,GAAgCD,EAAc5B,EAAO,CAE1D,IAAMgE,EACF,OAAO,OAAOpC,EAAK,MAAM,EACpB,OAAOqC,GAAM,CAACA,EAAE,YAAc,CAACA,EAAE,UAAW,EAC5C,IAAI,CAAC,CAAE,UAAAC,CAAU,IAAOA,CAAU,EAE3C,QAAWzC,KAAQzB,EAAO,CACtB,GAAM,CAAE,OAAAO,EAAQ,KAAA4D,CAAK,EAAI1C,EACzB,GAAI0C,GAAQA,EAAK,OAAQ,CACrB,IAAMlC,EAASkC,EAAK,OACpB,QAAWC,KAAUJ,EACjB,GAAI,CAAC/B,EAAO,SAASmC,CAAM,EACvB,MAAM,IAAIC,EAAwBD,EAAQ7D,CAAM,CAG5D,CACJ,CACJ,CAEA,IAAO+D,MAAQ,EAAAC,SAAGpF,EAAI","names":["index_exports","__export","index_default","__toCommonJS","import_fastify_plugin","import_leven","fastifyUser","findRule","rules","roles","found","rule","role","find_rule_default","getRequestFromContext","ctx","getRoles","request","roleKey","anonymousRole","isRolePath","output","user","rolesRaw","roleKeys","key","import_error","ERROR_PREFIX","Unauthorized","createError","UnauthorizedField","MissingNotNullableError","import_fastify_logto","PLT_ADMIN_ROLE","auth","app","opts","fastifyLogto","fastifyUser","adminSecret","roleKey","userKey","isRolePath","anonymousRole","composeLogToRules","rolesResp","roles","rules","role","scopesResp","scopes","scope","roleName","scopeAction","entity","nearest","findNearestEntity","checkExists","r","newRule","setupUser","forceAdminRole","target","key","value","newKey","ruleEntity","acc","distance","leven","entityRules","i","rule","ruleEntities","entityKey","type","checkSaveMandatoryFieldsInRules","originalFind","where","ctx","fields","restOpts","request","getRequestFromContext","findRuleForRequestUser","checkFieldsFromRule","fromRuleToWhere","originalSave","input","Unauthorized","checkInputFromRuleFields","defaults","hasAllPrimaryKeys","whereConditions","originalInsert","inputs","originalDelete","originalUpdateMany","user","checks","clauses","clauseKey","clause","getRoles","find_rule_default","fieldsFromRule","field","UnauthorizedField","validateInputs","inputFields","inputField","mandatoryFields","k","camelcase","save","mField","MissingNotNullableError","index_default","fp"]}
@@ -0,0 +1,32 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ import { FastifyUserPluginOptions } from 'fastify-user';
3
+
4
+ type PlatformaticRule = {
5
+ role: string;
6
+ entity?: string;
7
+ entities?: string[];
8
+ defaults?: Record<string, string>;
9
+ checks?: boolean;
10
+ find?: boolean;
11
+ save?: boolean;
12
+ delete?: boolean;
13
+ };
14
+ type PlatformaticLogtoAuthOptions = {
15
+ logtoBaseUrl?: string;
16
+ logtoAppId?: string;
17
+ logtoAppSecret?: string;
18
+ adminSecret?: string;
19
+ rolePath?: string;
20
+ roleKey?: string;
21
+ userPath?: string;
22
+ userKey?: string;
23
+ anonymousRole?: string;
24
+ allowAnonymous?: boolean;
25
+ checks?: boolean;
26
+ defaults?: boolean;
27
+ jwtPlugin: FastifyUserPluginOptions;
28
+ };
29
+ declare function auth(app: FastifyInstance, opts: PlatformaticLogtoAuthOptions): Promise<void>;
30
+ declare const _default: typeof auth;
31
+
32
+ export { type PlatformaticLogtoAuthOptions, type PlatformaticRule, _default as default };
package/lib/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { FastifyInstance } from 'fastify';
2
- import type { FastifyUserPluginOptions } from 'fastify-user';
3
- export type PlatformaticRule = {
2
+ import { FastifyUserPluginOptions } from 'fastify-user';
3
+
4
+ type PlatformaticRule = {
4
5
  role: string;
5
6
  entity?: string;
6
7
  entities?: string[];
@@ -10,7 +11,7 @@ export type PlatformaticRule = {
10
11
  save?: boolean;
11
12
  delete?: boolean;
12
13
  };
13
- export type PlatformaticLogtoAuthOptions = {
14
+ type PlatformaticLogtoAuthOptions = {
14
15
  logtoBaseUrl?: string;
15
16
  logtoAppId?: string;
16
17
  logtoAppSecret?: string;
@@ -27,4 +28,5 @@ export type PlatformaticLogtoAuthOptions = {
27
28
  };
28
29
  declare function auth(app: FastifyInstance, opts: PlatformaticLogtoAuthOptions): Promise<void>;
29
30
  declare const _default: typeof auth;
30
- export default _default;
31
+
32
+ export { type PlatformaticLogtoAuthOptions, type PlatformaticRule, _default as default };
package/lib/index.js CHANGED
@@ -1,420 +1,2 @@
1
- import fp from 'fastify-plugin';
2
- import leven from 'leven';
3
- import fastifyUser from 'fastify-user';
4
- import findRule from './utils/find-rule.js';
5
- import { getRequestFromContext, getRoles } from './utils/utils.js';
6
- import { Unauthorized, UnauthorizedField, MissingNotNullableError } from './utils/errors.js';
7
- import fastifyLogto from '@albirex/fastify-logto';
8
- const PLT_ADMIN_ROLE = 'platformatic-admin';
9
- async function auth(app, opts) {
10
- app.register(fastifyLogto, {
11
- endpoint: opts.logtoBaseUrl || 'https://auth.example.com',
12
- appId: opts.logtoAppId || 'your-app-id',
13
- appSecret: opts.logtoAppSecret || 'your-app-secret',
14
- });
15
- await app.register(fastifyUser.default, opts.jwtPlugin);
16
- const adminSecret = opts.adminSecret;
17
- const roleKey = opts.rolePath || opts.roleKey || 'X-PLATFORMATIC-ROLE';
18
- const userKey = opts.userPath || opts.userKey || 'X-PLATFORMATIC-USER-ID';
19
- const isRolePath = !!opts.rolePath; // if `true` the role is intepreted as path like `user.role`
20
- const anonymousRole = opts.anonymousRole || 'anonymous';
21
- async function composeLogToRules() {
22
- const rolesResp = await app.logto.callAPI('/api/roles?type=User', 'GET');
23
- if (!rolesResp.ok) {
24
- throw rolesResp;
25
- }
26
- const roles = await rolesResp.json();
27
- const rules = [{
28
- role: anonymousRole,
29
- entities: Object.keys(app.platformatic.entities),
30
- find: opts.allowAnonymous,
31
- save: opts.allowAnonymous,
32
- delete: opts.allowAnonymous,
33
- }];
34
- for (const role of roles) {
35
- const scopesResp = await app.logto.callAPI(`/api/roles/${role.id}/scopes`, 'GET');
36
- if (!scopesResp.ok) {
37
- throw scopesResp;
38
- }
39
- const scopes = await scopesResp.json();
40
- for (const scope of scopes) {
41
- const roleName = role.name;
42
- const [scopeAction, entity] = scope.name.split(':');
43
- if (!app.platformatic.entities[entity]) {
44
- const nearest = findNearestEntity(entity);
45
- app.log.warn(`Unknown entity '${entity}' in authorization rule. Did you mean '${nearest.entity}'?`);
46
- continue;
47
- }
48
- const checkExists = rules.find(r => r.role === roleName && r.entity === entity);
49
- if (checkExists) {
50
- if (opts.checks) {
51
- checkExists[scopeAction] = {
52
- checks: {
53
- userId: userKey
54
- }
55
- };
56
- }
57
- else {
58
- checkExists[scopeAction] = true;
59
- }
60
- }
61
- else {
62
- const newRule = {
63
- role: roleName,
64
- entity,
65
- };
66
- if (opts.checks) {
67
- newRule[scopeAction] = {
68
- checks: {
69
- userId: userKey
70
- }
71
- };
72
- }
73
- else {
74
- newRule[scopeAction] = true;
75
- }
76
- if (opts.defaults) {
77
- newRule.defaults = {
78
- userId: userKey
79
- };
80
- }
81
- rules.push(newRule);
82
- }
83
- }
84
- }
85
- app.log.debug('LogTo calculated rules');
86
- app.log.debug(rules);
87
- return rules;
88
- }
89
- app.decorateRequest('setupDBAuthorizationUser', setupUser);
90
- async function setupUser() {
91
- // if (!adminSecret) {
92
- await this.extractUser();
93
- // }
94
- let forceAdminRole = false;
95
- if (adminSecret && this.headers['x-platformatic-admin-secret'] === adminSecret) {
96
- if (opts.jwtPlugin.jwt) {
97
- forceAdminRole = true;
98
- }
99
- else {
100
- this.log.info('admin secret is valid');
101
- this.user = new Proxy(this.headers, {
102
- get: (target, key) => {
103
- let value;
104
- if (!target[key.toString()]) {
105
- const newKey = key.toString().toLowerCase();
106
- value = target[newKey];
107
- }
108
- else {
109
- value = target[key.toString()];
110
- }
111
- if (!value && key.toString().toLowerCase() === roleKey.toLowerCase()) {
112
- value = PLT_ADMIN_ROLE;
113
- }
114
- return value;
115
- },
116
- });
117
- }
118
- }
119
- if (forceAdminRole) {
120
- // We replace just the role in `request.user`, all the rest is untouched
121
- this.user = {
122
- // ...request.user,
123
- [roleKey]: PLT_ADMIN_ROLE,
124
- };
125
- }
126
- }
127
- function findNearestEntity(ruleEntity) {
128
- // There is an unknown entity. Let's find out the nearest one for a nice error message
129
- const entities = Object.keys(app.platformatic.entities);
130
- const nearest = entities.reduce((acc, entity) => {
131
- const distance = leven(ruleEntity, entity);
132
- if (distance < acc.distance) {
133
- acc.distance = distance;
134
- acc.entity = entity;
135
- }
136
- return acc;
137
- }, { distance: Infinity, entity: null });
138
- return nearest;
139
- }
140
- app.addHook('onReady', async function () {
141
- const rules = await composeLogToRules();
142
- // TODO validate that there is at most a rule for a given role
143
- const entityRules = {};
144
- for (let i = 0; i < rules.length; i++) {
145
- const rule = rules[i];
146
- let ruleEntities = null;
147
- if (rule.entity) {
148
- ruleEntities = [rule.entity];
149
- }
150
- else if (rule.entities) {
151
- ruleEntities = [...rule.entities];
152
- }
153
- else {
154
- throw new Error(`Missing entity in authorization rule ${i}`);
155
- }
156
- for (const ruleEntity of ruleEntities) {
157
- const newRule = { ...rule, entity: ruleEntity, entities: undefined };
158
- if (!app.platformatic.entities[newRule.entity]) {
159
- const nearest = findNearestEntity(ruleEntity);
160
- throw new Error(`Unknown entity '${ruleEntity}' in authorization rule ${i}. Did you mean '${nearest.entity}'?`);
161
- }
162
- if (!entityRules[ruleEntity]) {
163
- entityRules[ruleEntity] = [];
164
- }
165
- entityRules[ruleEntity].push(newRule);
166
- }
167
- }
168
- for (const entityKey of Object.keys(app.platformatic.entities)) {
169
- const rules = entityRules[entityKey] || [];
170
- const type = app.platformatic.entities[entityKey];
171
- // We have subscriptions!
172
- let userPropToFillForPublish;
173
- const topicsWithoutChecks = false;
174
- // mqtt
175
- // if (app.platformatic.mq) {
176
- // for (const rule of rules) {
177
- // const checks = rule.find?.checks
178
- // if (typeof checks !== 'object') {
179
- // topicsWithoutChecks = !!rule.find
180
- // continue
181
- // }
182
- // const keys = Object.keys(checks)
183
- // if (keys.length !== 1) {
184
- // throw new Error(`Subscription requires that the role "${rule.role}" has only one check in the find rule for entity "${rule.entity}"`)
185
- // }
186
- // const key = keys[0]
187
- // const val = typeof checks[key] === 'object' ? checks[key].eq : checks[key]
188
- // if (userPropToFillForPublish && userPropToFillForPublish.val !== val) {
189
- // throw new Error('Unable to configure subscriptions and authorization due to multiple check clauses in find')
190
- // }
191
- // userPropToFillForPublish = { key, val }
192
- // }
193
- // }
194
- if (userPropToFillForPublish && topicsWithoutChecks) {
195
- throw new Error(`Subscription for entity "${entityKey}" have conflictling rules across roles`);
196
- }
197
- // MUST set this after doing the security checks on the subscriptions
198
- if (adminSecret) {
199
- rules.push({
200
- role: PLT_ADMIN_ROLE,
201
- find: true,
202
- save: true,
203
- delete: true,
204
- });
205
- }
206
- // If we have `fields` in save rules, we need to check if all the not-nullable
207
- // fields are specified
208
- checkSaveMandatoryFieldsInRules(type, rules);
209
- // function useOriginal(skipAuth: boolean, ctx: PlatformaticContext) {
210
- // if (skipAuth === false && !ctx) {
211
- // throw new Error('Cannot set skipAuth to `false` without ctx')
212
- // }
213
- // return skipAuth || !ctx
214
- // }
215
- app.platformatic.addEntityHooks(entityKey, {
216
- async find(originalFind, { where, ctx, fields, ...restOpts } = {}) {
217
- // if (useOriginal(skipAuth, ctx)) {
218
- // return originalFind({ ...restOpts, where, ctx, fields })
219
- // }
220
- const request = getRequestFromContext(ctx);
221
- const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath);
222
- checkFieldsFromRule(rule.find, fields || Object.keys(app.platformatic.entities[entityKey].fields));
223
- where = await fromRuleToWhere(ctx, rule.find, where, request.user);
224
- return originalFind({ ...restOpts, where, ctx, fields });
225
- },
226
- async save(originalSave, { input, ctx, fields, ...restOpts }) {
227
- // if (useOriginal(skipAuth, ctx)) {
228
- // return originalSave({ ctx, input, fields, ...restOpts })
229
- // }
230
- const request = getRequestFromContext(ctx);
231
- const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath);
232
- if (!rule.save) {
233
- throw new Unauthorized();
234
- }
235
- checkFieldsFromRule(rule.save, fields);
236
- checkInputFromRuleFields(rule.save, input);
237
- if (rule.defaults) {
238
- for (const key of Object.keys(rule.defaults)) {
239
- const defaults = rule.defaults[key];
240
- if (typeof defaults === 'function') {
241
- input[key] = await defaults({ user: request.user, ctx, input });
242
- }
243
- else {
244
- input[key] = request.user[defaults];
245
- }
246
- }
247
- }
248
- const hasAllPrimaryKeys = input[type.primaryKey] !== undefined;
249
- const whereConditions = {};
250
- whereConditions[type.primaryKey] = { eq: input[type.primaryKey] };
251
- if (hasAllPrimaryKeys) {
252
- const where = await fromRuleToWhere(ctx, rule.save, whereConditions, request.user);
253
- const found = await type.find({
254
- where,
255
- ctx,
256
- fields,
257
- });
258
- if (found.length === 0) {
259
- throw new Unauthorized();
260
- }
261
- return originalSave({ input, ctx, fields, ...restOpts });
262
- }
263
- return originalSave({ input, ctx, fields, ...restOpts });
264
- },
265
- async insert(originalInsert, { inputs, ctx, fields, ...restOpts }) {
266
- // if (useOriginal(skipAuth, ctx)) {
267
- // return originalInsert({ inputs, ctx, fields, ...restOpts })
268
- // }
269
- const request = getRequestFromContext(ctx);
270
- const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath);
271
- if (!rule.save) {
272
- throw new Unauthorized();
273
- }
274
- checkFieldsFromRule(rule.save, fields);
275
- checkInputFromRuleFields(rule.save, inputs);
276
- /* istanbul ignore else */
277
- if (rule.defaults) {
278
- for (const input of inputs) {
279
- for (const key of Object.keys(rule.defaults)) {
280
- const defaults = rule.defaults[key];
281
- if (typeof defaults === 'function') {
282
- input[key] = await defaults({ user: request.user, ctx, input });
283
- }
284
- else {
285
- input[key] = request.user[defaults];
286
- }
287
- }
288
- }
289
- }
290
- return originalInsert({ inputs, ctx, fields, ...restOpts });
291
- },
292
- async delete(originalDelete, { where, ctx, fields, ...restOpts }) {
293
- // if (useOriginal(skipAuth, ctx)) {
294
- // return originalDelete({ where, ctx, fields, ...restOpts })
295
- // }
296
- const request = getRequestFromContext(ctx);
297
- const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath);
298
- where = await fromRuleToWhere(ctx, rule.delete, where, request.user);
299
- return originalDelete({ where, ctx, fields, ...restOpts });
300
- },
301
- async updateMany(originalUpdateMany, { where, ctx, fields, ...restOpts }) {
302
- // if (useOriginal(skipAuth, ctx)) {
303
- // return originalUpdateMany({ ...restOpts, where, ctx, fields })
304
- // }
305
- const request = getRequestFromContext(ctx);
306
- const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath);
307
- where = await fromRuleToWhere(ctx, rule.updateMany, where, request.user);
308
- return originalUpdateMany({ ...restOpts, where, ctx, fields });
309
- },
310
- });
311
- }
312
- });
313
- }
314
- async function fromRuleToWhere(ctx, rule, where, user) {
315
- if (!rule) {
316
- throw new Unauthorized();
317
- }
318
- const request = getRequestFromContext(ctx);
319
- /* istanbul ignore next */
320
- where = where || {};
321
- if (typeof rule === 'object') {
322
- const { checks } = rule;
323
- /* istanbul ignore else */
324
- if (checks) {
325
- for (const key of Object.keys(checks)) {
326
- const clauses = checks[key];
327
- if (typeof clauses === 'string') {
328
- // case: "userId": "X-PLATFORMATIC-USER-ID"
329
- where[key] = {
330
- eq: request.user[clauses],
331
- };
332
- }
333
- else {
334
- // case:
335
- // userId: {
336
- // eq: 'X-PLATFORMATIC-USER-ID'
337
- // }
338
- for (const clauseKey of Object.keys(clauses)) {
339
- const clause = clauses[clauseKey];
340
- where[key] = {
341
- [clauseKey]: request.user[clause],
342
- };
343
- }
344
- }
345
- }
346
- }
347
- }
348
- else if (typeof rule === 'function') {
349
- where = await rule({ user, ctx, where });
350
- }
351
- return where;
352
- }
353
- async function findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath = false) {
354
- const request = getRequestFromContext(ctx);
355
- await request.setupDBAuthorizationUser();
356
- const roles = getRoles(request, roleKey, anonymousRole, isRolePath);
357
- const rule = findRule(rules, roles);
358
- if (!rule) {
359
- ctx.reply.request.log.warn({ roles, rules }, 'no rule for roles');
360
- throw new Unauthorized();
361
- }
362
- ctx.reply.request.log.trace({ roles, rule }, 'found rule');
363
- return rule;
364
- }
365
- function checkFieldsFromRule(rule, fields) {
366
- if (!rule) {
367
- throw new Unauthorized();
368
- }
369
- const { fields: fieldsFromRule } = rule;
370
- /* istanbul ignore else */
371
- if (fieldsFromRule) {
372
- for (const field of fields) {
373
- if (!fieldsFromRule.includes(field)) {
374
- throw new UnauthorizedField(field);
375
- }
376
- }
377
- }
378
- }
379
- const validateInputs = (inputs, fieldsFromRule) => {
380
- for (const input of inputs) {
381
- const inputFields = Object.keys(input);
382
- for (const inputField of inputFields) {
383
- if (!fieldsFromRule.includes(inputField)) {
384
- throw new UnauthorizedField(inputField);
385
- }
386
- }
387
- }
388
- };
389
- function checkInputFromRuleFields(rule, inputs) {
390
- const { fields: fieldsFromRule } = rule;
391
- /* istanbul ignore else */
392
- if (fieldsFromRule) {
393
- if (!Array.isArray(inputs)) {
394
- // save
395
- validateInputs([inputs], fieldsFromRule);
396
- }
397
- else {
398
- // insert
399
- validateInputs(inputs, fieldsFromRule);
400
- }
401
- }
402
- }
403
- function checkSaveMandatoryFieldsInRules(type, rules) {
404
- // List of not nullable, not PKs field to validate save/insert when allowed fields are specified on the rule
405
- const mandatoryFields = Object.values(type.fields)
406
- .filter(k => (!k.isNullable && !k.primaryKey))
407
- .map(({ camelcase }) => (camelcase));
408
- for (const rule of rules) {
409
- const { entity, save } = rule;
410
- if (save && save.fields) {
411
- const fields = save.fields;
412
- for (const mField of mandatoryFields) {
413
- if (!fields.includes(mField)) {
414
- throw new MissingNotNullableError(mField, entity);
415
- }
416
- }
417
- }
418
- }
419
- }
420
- export default fp(auth);
1
+ import B from"fastify-plugin";import H from"leven";import*as G from"fastify-user";function z(t,e){let s=null;for(let r of t){for(let a of e)if(r.role===a){s=r;break}if(s)break}return s}var N=z;function A(t){if(t&&!t.reply)throw new Error("Missing reply in context. You should call this function with { ctx: { reply }}");return t.reply.request}function M(t,e,s,r=!1){let a=[],c=t.user;if(!c)return a.push(s),a;let l;if(r){let h=e.split(".");l=c;for(let F of h)l=l[F]}else l=c[e];return typeof l=="string"?a=l.split(","):Array.isArray(l)&&(a=l),a.length===0&&a.push(s),a}import q from"@fastify/error";var T="PLT_DB_AUTH",b=q(`${T}_UNAUTHORIZED`,"operation not allowed",401),K=q(`${T}_FIELD_UNAUTHORIZED`,"field not allowed: %s",401),_=q(`${T}_NOT_NULLABLE_MISSING`,'missing not nullable field: "%s" in save rule for entity "%s"');import W from"@albirex/fastify-logto";var S="platformatic-admin";async function X(t,e){t.register(W,{endpoint:e.logtoBaseUrl||"https://auth.example.com",appId:e.logtoAppId||"your-app-id",appSecret:e.logtoAppSecret||"your-app-secret"}),await t.register(G,e.jwtPlugin);let s=e.adminSecret,r=e.rolePath||e.roleKey||"X-PLATFORMATIC-ROLE",a=e.userPath||e.userKey||"X-PLATFORMATIC-USER-ID",c=!!e.rolePath,l=e.anonymousRole||"anonymous";async function h(){let R=await t.logto.callAPI("/api/roles?type=User","GET");if(!R.ok)throw R;let P=await R.json(),f=[{role:l,entities:Object.keys(t.platformatic.entities),find:e.allowAnonymous,save:e.allowAnonymous,delete:e.allowAnonymous}];for(let i of P){let d=await t.logto.callAPI(`/api/roles/${i.id}/scopes`,"GET");if(!d.ok)throw d;let w=await d.json();for(let I of w){let g=i.name,[n,o]=I.name.split(":");if(!t.platformatic.entities[o]){let y=v(o);t.log.warn(`Unknown entity '${o}' in authorization rule. Did you mean '${y.entity}'?`);continue}let m=f.find(y=>y.role===g&&y.entity===o);if(m)e.checks?m[n]={checks:{userId:a}}:m[n]=!0;else{let y={role:g,entity:o};e.checks?y[n]={checks:{userId:a}}:y[n]=!0,e.defaults&&(y.defaults={userId:a}),f.push(y)}}}return t.log.debug("LogTo calculated rules"),t.log.debug(f),f}t.decorateRequest("setupDBAuthorizationUser",F);async function F(){await this.extractUser();let R=!1;s&&this.headers["x-platformatic-admin-secret"]===s&&(e.jwtPlugin.jwt?R=!0:(this.log.info("admin secret is valid"),this.user=new Proxy(this.headers,{get:(P,f)=>{let i;if(P[f.toString()])i=P[f.toString()];else{let d=f.toString().toLowerCase();i=P[d]}return!i&&f.toString().toLowerCase()===r.toLowerCase()&&(i=S),i}}))),R&&(this.user={[r]:S})}function v(R){return Object.keys(t.platformatic.entities).reduce((i,d)=>{let w=H(R,d);return w<i.distance&&(i.distance=w,i.entity=d),i},{distance:1/0,entity:null})}t.addHook("onReady",async function(){let R=await h(),P={};for(let f=0;f<R.length;f++){let i=R[f],d=null;if(i.entity)d=[i.entity];else if(i.entities)d=[...i.entities];else throw new Error(`Missing entity in authorization rule ${f}`);for(let w of d){let I={...i,entity:w,entities:void 0};if(!t.platformatic.entities[I.entity]){let g=v(w);throw new Error(`Unknown entity '${w}' in authorization rule ${f}. Did you mean '${g.entity}'?`)}P[w]||(P[w]=[]),P[w].push(I)}}for(let f of Object.keys(t.platformatic.entities)){let i=P[f]||[],d=t.platformatic.entities[f];if(void 0&&!1)throw new Error(`Subscription for entity "${f}" have conflictling rules across roles`);s&&i.push({role:S,find:!0,save:!0,delete:!0}),Z(d,i),t.platformatic.addEntityHooks(f,{async find(g,{where:n,ctx:o,fields:m,...y}={}){let p=A(o),u=await O(o,i,r,l,c);return C(u.find,m||Object.keys(t.platformatic.entities[f].fields)),n=await j(o,u.find,n,p.user),g({...y,where:n,ctx:o,fields:m})},async save(g,{input:n,ctx:o,fields:m,...y}){let p=A(o),u=await O(o,i,r,l,c);if(!u.save)throw new b;if(C(u.save,m),D(u.save,n),u.defaults)for(let k of Object.keys(u.defaults)){let L=u.defaults[k];typeof L=="function"?n[k]=await L({user:p.user,ctx:o,input:n}):n[k]=p.user[L]}let U=n[d.primaryKey]!==void 0,E={};if(E[d.primaryKey]={eq:n[d.primaryKey]},U){let k=await j(o,u.save,E,p.user);if((await d.find({where:k,ctx:o,fields:m})).length===0)throw new b;return g({input:n,ctx:o,fields:m,...y})}return g({input:n,ctx:o,fields:m,...y})},async insert(g,{inputs:n,ctx:o,fields:m,...y}){let p=A(o),u=await O(o,i,r,l,c);if(!u.save)throw new b;if(C(u.save,m),D(u.save,n),u.defaults)for(let U of n)for(let E of Object.keys(u.defaults)){let k=u.defaults[E];typeof k=="function"?U[E]=await k({user:p.user,ctx:o,input:U}):U[E]=p.user[k]}return g({inputs:n,ctx:o,fields:m,...y})},async delete(g,{where:n,ctx:o,fields:m,...y}){let p=A(o),u=await O(o,i,r,l,c);return n=await j(o,u.delete,n,p.user),g({where:n,ctx:o,fields:m,...y})},async updateMany(g,{where:n,ctx:o,fields:m,...y}){let p=A(o),u=await O(o,i,r,l,c);return n=await j(o,u.updateMany,n,p.user),g({...y,where:n,ctx:o,fields:m})}})}})}async function j(t,e,s,r){if(!e)throw new b;let a=A(t);if(s=s||{},typeof e=="object"){let{checks:c}=e;if(c)for(let l of Object.keys(c)){let h=c[l];if(typeof h=="string")s[l]={eq:a.user[h]};else for(let F of Object.keys(h)){let v=h[F];s[l]={[F]:a.user[v]}}}}else typeof e=="function"&&(s=await e({user:r,ctx:t,where:s}));return s}async function O(t,e,s,r,a=!1){let c=A(t);await c.setupDBAuthorizationUser();let l=M(c,s,r,a),h=N(e,l);if(!h)throw t.reply.request.log.warn({roles:l,rules:e},"no rule for roles"),new b;return t.reply.request.log.trace({roles:l,rule:h},"found rule"),h}function C(t,e){if(!t)throw new b;let{fields:s}=t;if(s){for(let r of e)if(!s.includes(r))throw new K(r)}}var $=(t,e)=>{for(let s of t){let r=Object.keys(s);for(let a of r)if(!e.includes(a))throw new K(a)}};function D(t,e){let{fields:s}=t;s&&(Array.isArray(e)?$(e,s):$([e],s))}function Z(t,e){let s=Object.values(t.fields).filter(r=>!r.isNullable&&!r.primaryKey).map(({camelcase:r})=>r);for(let r of e){let{entity:a,save:c}=r;if(c&&c.fields){let l=c.fields;for(let h of s)if(!l.includes(h))throw new _(h,a)}}}var re=B(X);export{re as default};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/find-rule.ts","../src/utils/utils.ts","../src/utils/errors.ts"],"sourcesContent":["import fp from 'fastify-plugin'\nimport leven from 'leven'\nimport * as fastifyUser from 'fastify-user'\n\nimport findRule from './utils/find-rule.js'\nimport { getRequestFromContext, getRoles } from './utils/utils.js'\nimport { Unauthorized, UnauthorizedField, MissingNotNullableError } from './utils/errors.js'\nimport fastifyLogto from '@albirex/fastify-logto';\nimport { FastifyInstance } from 'fastify'\nimport type { FastifyUserPluginOptions } from 'fastify-user';\nimport type { Entity, PlatformaticContext } from '@platformatic/sql-mapper'\n\nconst PLT_ADMIN_ROLE = 'platformatic-admin'\n\nexport type PlatformaticRule = {\n role: string;\n entity?: string;\n entities?: string[];\n defaults?: Record<string, string>;\n checks?: boolean;\n find?: boolean;\n save?: boolean;\n delete?: boolean;\n\n};\n\nexport type PlatformaticLogtoAuthOptions = {\n logtoBaseUrl?: string;\n logtoAppId?: string;\n logtoAppSecret?: string;\n adminSecret?: string;\n rolePath?: string;\n roleKey?: string;\n userPath?: string;\n userKey?: string;\n anonymousRole?: string;\n allowAnonymous?: boolean;\n checks?: boolean;\n defaults?: boolean;\n jwtPlugin: FastifyUserPluginOptions\n};\n\nasync function auth(app: FastifyInstance, opts: PlatformaticLogtoAuthOptions) {\n app.register(fastifyLogto, {\n endpoint: opts.logtoBaseUrl || 'https://auth.example.com',\n appId: opts.logtoAppId || 'your-app-id',\n appSecret: opts.logtoAppSecret || 'your-app-secret',\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await app.register(fastifyUser as any, opts.jwtPlugin);\n\n const adminSecret = opts.adminSecret\n const roleKey = opts.rolePath || opts.roleKey || 'X-PLATFORMATIC-ROLE'\n const userKey = opts.userPath || opts.userKey || 'X-PLATFORMATIC-USER-ID'\n const isRolePath = !!opts.rolePath // if `true` the role is intepreted as path like `user.role`\n const anonymousRole = opts.anonymousRole || 'anonymous'\n\n async function composeLogToRules() {\n const rolesResp = await app.logto.callAPI('/api/roles?type=User', 'GET');\n\n if (!rolesResp.ok) {\n throw rolesResp;\n }\n\n const roles = await rolesResp.json();\n const rules: PlatformaticRule[] = [{\n role: anonymousRole,\n entities: Object.keys(app.platformatic.entities),\n find: opts.allowAnonymous,\n save: opts.allowAnonymous,\n delete: opts.allowAnonymous,\n }];\n\n for (const role of roles) {\n const scopesResp = await app.logto.callAPI(`/api/roles/${role.id}/scopes`, 'GET');\n\n if (!scopesResp.ok) {\n throw scopesResp;\n }\n\n const scopes = await scopesResp.json();\n\n for (const scope of scopes) {\n const roleName = role.name;\n const [scopeAction, entity] = scope.name.split(':');\n\n if (!app.platformatic.entities[entity]) {\n const nearest = findNearestEntity(entity)\n app.log.warn(`Unknown entity '${entity}' in authorization rule. Did you mean '${nearest.entity}'?`)\n continue;\n }\n\n const checkExists = rules.find(r => r.role === roleName && r.entity === entity);\n if (checkExists) {\n if (opts.checks) {\n checkExists[scopeAction] = {\n checks: {\n userId: userKey\n }\n };\n } else {\n checkExists[scopeAction] = true;\n }\n } else {\n const newRule: PlatformaticRule = {\n role: roleName,\n entity,\n };\n\n if (opts.checks) {\n newRule[scopeAction] = {\n checks: {\n userId: userKey\n }\n };\n } else {\n newRule[scopeAction] = true;\n }\n\n if (opts.defaults) {\n newRule.defaults = {\n userId: userKey\n };\n }\n\n rules.push(newRule);\n }\n }\n }\n\n app.log.debug('LogTo calculated rules');\n app.log.debug(rules);\n return rules;\n }\n\n app.decorateRequest('setupDBAuthorizationUser', setupUser)\n\n async function setupUser() {\n // if (!adminSecret) {\n await this.extractUser()\n // }\n\n let forceAdminRole = false\n if (adminSecret && this.headers['x-platformatic-admin-secret'] === adminSecret) {\n if (opts.jwtPlugin.jwt) {\n forceAdminRole = true\n } else {\n this.log.info('admin secret is valid')\n this.user = new Proxy(this.headers, {\n get: (target, key) => {\n let value;\n if (!target[key.toString()]) {\n const newKey = key.toString().toLowerCase()\n value = target[newKey]\n } else {\n value = target[key.toString()]\n }\n\n if (!value && key.toString().toLowerCase() === roleKey.toLowerCase()) {\n value = PLT_ADMIN_ROLE\n }\n return value\n },\n })\n }\n }\n\n if (forceAdminRole) {\n // We replace just the role in `request.user`, all the rest is untouched\n this.user = {\n // ...request.user,\n [roleKey]: PLT_ADMIN_ROLE,\n }\n }\n }\n\n function findNearestEntity(ruleEntity) {\n // There is an unknown entity. Let's find out the nearest one for a nice error message\n const entities = Object.keys(app.platformatic.entities)\n\n const nearest = entities.reduce((acc, entity) => {\n const distance = leven(ruleEntity, entity)\n if (distance < acc.distance) {\n acc.distance = distance\n acc.entity = entity\n }\n return acc\n }, { distance: Infinity, entity: null })\n return nearest\n }\n\n app.addHook('onReady', async function () {\n const rules = await composeLogToRules();\n\n // TODO validate that there is at most a rule for a given role\n const entityRules = {};\n for (let i = 0; i < rules.length; i++) {\n const rule = rules[i]\n\n let ruleEntities = null\n if (rule.entity) {\n ruleEntities = [rule.entity]\n } else if (rule.entities) {\n ruleEntities = [...rule.entities]\n } else {\n throw new Error(`Missing entity in authorization rule ${i}`)\n }\n\n for (const ruleEntity of ruleEntities) {\n const newRule = { ...rule, entity: ruleEntity, entities: undefined }\n if (!app.platformatic.entities[newRule.entity]) {\n const nearest = findNearestEntity(ruleEntity)\n throw new Error(`Unknown entity '${ruleEntity}' in authorization rule ${i}. Did you mean '${nearest.entity}'?`)\n }\n\n if (!entityRules[ruleEntity]) {\n entityRules[ruleEntity] = []\n }\n entityRules[ruleEntity].push(newRule)\n }\n }\n\n for (const entityKey of Object.keys(app.platformatic.entities)) {\n const rules = entityRules[entityKey] || []\n const type = app.platformatic.entities[entityKey]\n\n // We have subscriptions!\n let userPropToFillForPublish\n const topicsWithoutChecks = false\n\n // mqtt\n // if (app.platformatic.mq) {\n // for (const rule of rules) {\n // const checks = rule.find?.checks\n // if (typeof checks !== 'object') {\n // topicsWithoutChecks = !!rule.find\n // continue\n // }\n // const keys = Object.keys(checks)\n // if (keys.length !== 1) {\n // throw new Error(`Subscription requires that the role \"${rule.role}\" has only one check in the find rule for entity \"${rule.entity}\"`)\n // }\n // const key = keys[0]\n\n // const val = typeof checks[key] === 'object' ? checks[key].eq : checks[key]\n // if (userPropToFillForPublish && userPropToFillForPublish.val !== val) {\n // throw new Error('Unable to configure subscriptions and authorization due to multiple check clauses in find')\n // }\n // userPropToFillForPublish = { key, val }\n // }\n // }\n\n if (userPropToFillForPublish && topicsWithoutChecks) {\n throw new Error(`Subscription for entity \"${entityKey}\" have conflictling rules across roles`)\n }\n\n // MUST set this after doing the security checks on the subscriptions\n if (adminSecret) {\n rules.push({\n role: PLT_ADMIN_ROLE,\n find: true,\n save: true,\n delete: true,\n })\n }\n\n // If we have `fields` in save rules, we need to check if all the not-nullable\n // fields are specified\n checkSaveMandatoryFieldsInRules(type, rules)\n\n // function useOriginal(skipAuth: boolean, ctx: PlatformaticContext) {\n // if (skipAuth === false && !ctx) {\n // throw new Error('Cannot set skipAuth to `false` without ctx')\n // }\n\n // return skipAuth || !ctx\n // }\n\n app.platformatic.addEntityHooks(entityKey, {\n async find(originalFind, { where, ctx, fields, ...restOpts } = {}) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalFind({ ...restOpts, where, ctx, fields })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n checkFieldsFromRule(rule.find, fields || Object.keys(app.platformatic.entities[entityKey].fields))\n where = await fromRuleToWhere(ctx, rule.find, where, request.user)\n\n return originalFind({ ...restOpts, where, ctx, fields })\n },\n async save(originalSave, { input, ctx, fields, ...restOpts }) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalSave({ ctx, input, fields, ...restOpts })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n\n if (!rule.save) {\n throw new Unauthorized()\n }\n checkFieldsFromRule(rule.save, fields)\n checkInputFromRuleFields(rule.save, input)\n\n if (rule.defaults) {\n for (const key of Object.keys(rule.defaults)) {\n const defaults = rule.defaults[key]\n if (typeof defaults === 'function') {\n input[key] = await defaults({ user: request.user, ctx, input })\n } else {\n input[key] = request.user[defaults]\n }\n }\n }\n\n const hasAllPrimaryKeys = input[type.primaryKey] !== undefined;\n const whereConditions = {}\n whereConditions[type.primaryKey] = { eq: input[type.primaryKey] }\n\n if (hasAllPrimaryKeys) {\n const where = await fromRuleToWhere(ctx, rule.save, whereConditions, request.user)\n\n const found = await type.find({\n where,\n ctx,\n fields,\n })\n\n if (found.length === 0) {\n throw new Unauthorized()\n }\n\n return originalSave({ input, ctx, fields, ...restOpts })\n }\n\n return originalSave({ input, ctx, fields, ...restOpts })\n },\n\n async insert(originalInsert, { inputs, ctx, fields, ...restOpts }) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalInsert({ inputs, ctx, fields, ...restOpts })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n\n if (!rule.save) {\n throw new Unauthorized()\n }\n\n checkFieldsFromRule(rule.save, fields)\n checkInputFromRuleFields(rule.save, inputs)\n\n /* istanbul ignore else */\n if (rule.defaults) {\n for (const input of inputs) {\n for (const key of Object.keys(rule.defaults)) {\n const defaults = rule.defaults[key]\n if (typeof defaults === 'function') {\n input[key] = await defaults({ user: request.user, ctx, input })\n } else {\n input[key] = request.user[defaults]\n }\n }\n }\n }\n\n return originalInsert({ inputs, ctx, fields, ...restOpts })\n },\n\n async delete(originalDelete, { where, ctx, fields, ...restOpts }) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalDelete({ where, ctx, fields, ...restOpts })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n\n where = await fromRuleToWhere(ctx, rule.delete, where, request.user)\n\n return originalDelete({ where, ctx, fields, ...restOpts })\n },\n\n async updateMany(originalUpdateMany, { where, ctx, fields, ...restOpts }) {\n // if (useOriginal(skipAuth, ctx)) {\n // return originalUpdateMany({ ...restOpts, where, ctx, fields })\n // }\n const request = getRequestFromContext(ctx)\n const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)\n\n where = await fromRuleToWhere(ctx, rule.updateMany, where, request.user)\n\n return originalUpdateMany({ ...restOpts, where, ctx, fields })\n },\n })\n }\n })\n}\n\nasync function fromRuleToWhere(ctx: PlatformaticContext, rule, where, user) {\n if (!rule) {\n throw new Unauthorized()\n }\n const request = getRequestFromContext(ctx)\n /* istanbul ignore next */\n where = where || {}\n\n if (typeof rule === 'object') {\n const { checks } = rule\n\n /* istanbul ignore else */\n if (checks) {\n for (const key of Object.keys(checks)) {\n const clauses = checks[key]\n if (typeof clauses === 'string') {\n // case: \"userId\": \"X-PLATFORMATIC-USER-ID\"\n where[key] = {\n eq: request.user[clauses],\n }\n } else {\n // case:\n // userId: {\n // eq: 'X-PLATFORMATIC-USER-ID'\n // }\n for (const clauseKey of Object.keys(clauses)) {\n const clause = clauses[clauseKey]\n where[key] = {\n [clauseKey]: request.user[clause],\n }\n }\n }\n }\n }\n } else if (typeof rule === 'function') {\n where = await rule({ user, ctx, where })\n }\n return where\n}\n\nasync function findRuleForRequestUser(ctx: PlatformaticContext, rules: PlatformaticRule[], roleKey: string, anonymousRole: string, isRolePath = false) {\n const request = getRequestFromContext(ctx)\n await request.setupDBAuthorizationUser()\n const roles = getRoles(request, roleKey, anonymousRole, isRolePath)\n const rule = findRule(rules, roles)\n if (!rule) {\n ctx.reply.request.log.warn({ roles, rules }, 'no rule for roles')\n throw new Unauthorized()\n }\n ctx.reply.request.log.trace({ roles, rule }, 'found rule')\n return rule\n}\n\nfunction checkFieldsFromRule(rule, fields) {\n if (!rule) {\n throw new Unauthorized()\n }\n const { fields: fieldsFromRule } = rule\n /* istanbul ignore else */\n if (fieldsFromRule) {\n for (const field of fields) {\n if (!fieldsFromRule.includes(field)) {\n throw new UnauthorizedField(field)\n }\n }\n }\n}\n\nconst validateInputs = (inputs, fieldsFromRule) => {\n for (const input of inputs) {\n const inputFields = Object.keys(input)\n for (const inputField of inputFields) {\n if (!fieldsFromRule.includes(inputField)) {\n throw new UnauthorizedField(inputField)\n }\n }\n }\n}\n\nfunction checkInputFromRuleFields(rule, inputs) {\n const { fields: fieldsFromRule } = rule\n /* istanbul ignore else */\n if (fieldsFromRule) {\n if (!Array.isArray(inputs)) {\n // save\n validateInputs([inputs], fieldsFromRule)\n } else {\n // insert\n validateInputs(inputs, fieldsFromRule)\n }\n }\n}\n\nfunction checkSaveMandatoryFieldsInRules(type: Entity, rules) {\n // List of not nullable, not PKs field to validate save/insert when allowed fields are specified on the rule\n const mandatoryFields =\n Object.values(type.fields)\n .filter(k => (!k.isNullable && !k.primaryKey))\n .map(({ camelcase }) => (camelcase))\n\n for (const rule of rules) {\n const { entity, save } = rule\n if (save && save.fields) {\n const fields = save.fields\n for (const mField of mandatoryFields) {\n if (!fields.includes(mField)) {\n throw new MissingNotNullableError(mField, entity)\n }\n }\n }\n }\n}\n\nexport default fp(auth)\n","'use strict'\n\nimport { PlatformaticRule } from '../index.js'\n\nfunction findRule(rules: PlatformaticRule[], roles: string[]) {\n let found = null\n for (const rule of rules) {\n for (const role of roles) {\n if (rule.role === role) {\n found = rule\n break\n }\n }\n if (found) {\n break\n }\n }\n return found\n}\n\nexport default findRule\n","'use strict'\n\nexport function getRequestFromContext (ctx) {\n if (ctx && !ctx.reply) {\n throw new Error('Missing reply in context. You should call this function with { ctx: { reply }}')\n }\n return ctx.reply.request\n}\n\nexport function getRoles (request, roleKey, anonymousRole, isRolePath = false) {\n let output = []\n const user = request.user\n if (!user) {\n output.push(anonymousRole)\n return output\n }\n\n let rolesRaw\n if (isRolePath) {\n const roleKeys = roleKey.split('.')\n rolesRaw = user\n for (const key of roleKeys) {\n rolesRaw = rolesRaw[key]\n }\n } else {\n rolesRaw = user[roleKey]\n }\n\n if (typeof rolesRaw === 'string') {\n output = rolesRaw.split(',')\n } else if (Array.isArray(rolesRaw)) {\n output = rolesRaw\n }\n if (output.length === 0) {\n output.push(anonymousRole)\n }\n\n return output\n}\n","'use strict'\n\nimport createError from '@fastify/error'\n\nconst ERROR_PREFIX = 'PLT_DB_AUTH'\n\nexport const Unauthorized = createError(`${ERROR_PREFIX}_UNAUTHORIZED`, 'operation not allowed', 401)\nexport const UnauthorizedField = createError(`${ERROR_PREFIX}_FIELD_UNAUTHORIZED`, 'field not allowed: %s', 401)\nexport const MissingNotNullableError = createError(`${ERROR_PREFIX}_NOT_NULLABLE_MISSING`, 'missing not nullable field: \"%s\" in save rule for entity \"%s\"')\n"],"mappings":"AAAA,OAAOA,MAAQ,iBACf,OAAOC,MAAW,QAClB,UAAYC,MAAiB,eCE7B,SAASC,EAASC,EAA2BC,EAAiB,CAC5D,IAAIC,EAAQ,KACZ,QAAWC,KAAQH,EAAO,CACxB,QAAWI,KAAQH,EACjB,GAAIE,EAAK,OAASC,EAAM,CACtBF,EAAQC,EACR,KACF,CAEF,GAAID,EACF,KAEJ,CACA,OAAOA,CACT,CAEA,IAAOG,EAAQN,EClBR,SAASO,EAAuBC,EAAK,CAC1C,GAAIA,GAAO,CAACA,EAAI,MACd,MAAM,IAAI,MAAM,gFAAgF,EAElG,OAAOA,EAAI,MAAM,OACnB,CAEO,SAASC,EAAUC,EAASC,EAASC,EAAeC,EAAa,GAAO,CAC7E,IAAIC,EAAS,CAAC,EACRC,EAAOL,EAAQ,KACrB,GAAI,CAACK,EACH,OAAAD,EAAO,KAAKF,CAAa,EAClBE,EAGT,IAAIE,EACJ,GAAIH,EAAY,CACd,IAAMI,EAAWN,EAAQ,MAAM,GAAG,EAClCK,EAAWD,EACX,QAAWG,KAAOD,EAChBD,EAAWA,EAASE,CAAG,CAE3B,MACEF,EAAWD,EAAKJ,CAAO,EAGzB,OAAI,OAAOK,GAAa,SACtBF,EAASE,EAAS,MAAM,GAAG,EAClB,MAAM,QAAQA,CAAQ,IAC/BF,EAASE,GAEPF,EAAO,SAAW,GACpBA,EAAO,KAAKF,CAAa,EAGpBE,CACT,CCpCA,OAAOK,MAAiB,iBAExB,IAAMC,EAAe,cAERC,EAAeF,EAAY,GAAGC,CAAY,gBAAiB,wBAAyB,GAAG,EACvFE,EAAoBH,EAAY,GAAGC,CAAY,sBAAuB,wBAAyB,GAAG,EAClGG,EAA0BJ,EAAY,GAAGC,CAAY,wBAAyB,+DAA+D,EHD1J,OAAOI,MAAkB,yBAKzB,IAAMC,EAAiB,qBA8BvB,eAAeC,EAAKC,EAAsBC,EAAoC,CAC1ED,EAAI,SAASH,EAAc,CACvB,SAAUI,EAAK,cAAgB,2BAC/B,MAAOA,EAAK,YAAc,cAC1B,UAAWA,EAAK,gBAAkB,iBACtC,CAAC,EAED,MAAMD,EAAI,SAASE,EAAoBD,EAAK,SAAS,EAErD,IAAME,EAAcF,EAAK,YACnBG,EAAUH,EAAK,UAAYA,EAAK,SAAW,sBAC3CI,EAAUJ,EAAK,UAAYA,EAAK,SAAW,yBAC3CK,EAAa,CAAC,CAACL,EAAK,SACpBM,EAAgBN,EAAK,eAAiB,YAE5C,eAAeO,GAAoB,CAC/B,IAAMC,EAAY,MAAMT,EAAI,MAAM,QAAQ,uBAAwB,KAAK,EAEvE,GAAI,CAACS,EAAU,GACX,MAAMA,EAGV,IAAMC,EAAQ,MAAMD,EAAU,KAAK,EAC7BE,EAA4B,CAAC,CAC/B,KAAMJ,EACN,SAAU,OAAO,KAAKP,EAAI,aAAa,QAAQ,EAC/C,KAAMC,EAAK,eACX,KAAMA,EAAK,eACX,OAAQA,EAAK,cACjB,CAAC,EAED,QAAWW,KAAQF,EAAO,CACtB,IAAMG,EAAa,MAAMb,EAAI,MAAM,QAAQ,cAAcY,EAAK,EAAE,UAAW,KAAK,EAEhF,GAAI,CAACC,EAAW,GACZ,MAAMA,EAGV,IAAMC,EAAS,MAAMD,EAAW,KAAK,EAErC,QAAWE,KAASD,EAAQ,CACxB,IAAME,EAAWJ,EAAK,KAChB,CAACK,EAAaC,CAAM,EAAIH,EAAM,KAAK,MAAM,GAAG,EAElD,GAAI,CAACf,EAAI,aAAa,SAASkB,CAAM,EAAG,CACpC,IAAMC,EAAUC,EAAkBF,CAAM,EACxClB,EAAI,IAAI,KAAK,mBAAmBkB,CAAM,0CAA0CC,EAAQ,MAAM,IAAI,EAClG,QACJ,CAEA,IAAME,EAAcV,EAAM,KAAKW,GAAKA,EAAE,OAASN,GAAYM,EAAE,SAAWJ,CAAM,EAC9E,GAAIG,EACIpB,EAAK,OACLoB,EAAYJ,CAAW,EAAI,CACvB,OAAQ,CACJ,OAAQZ,CACZ,CACJ,EAEAgB,EAAYJ,CAAW,EAAI,OAE5B,CACH,IAAMM,EAA4B,CAC9B,KAAMP,EACN,OAAAE,CACJ,EAEIjB,EAAK,OACLsB,EAAQN,CAAW,EAAI,CACnB,OAAQ,CACJ,OAAQZ,CACZ,CACJ,EAEAkB,EAAQN,CAAW,EAAI,GAGvBhB,EAAK,WACLsB,EAAQ,SAAW,CACf,OAAQlB,CACZ,GAGJM,EAAM,KAAKY,CAAO,CACtB,CACJ,CACJ,CAEA,OAAAvB,EAAI,IAAI,MAAM,wBAAwB,EACtCA,EAAI,IAAI,MAAMW,CAAK,EACZA,CACX,CAEAX,EAAI,gBAAgB,2BAA4BwB,CAAS,EAEzD,eAAeA,GAAY,CAEvB,MAAM,KAAK,YAAY,EAGvB,IAAIC,EAAiB,GACjBtB,GAAe,KAAK,QAAQ,6BAA6B,IAAMA,IAC3DF,EAAK,UAAU,IACfwB,EAAiB,IAEjB,KAAK,IAAI,KAAK,uBAAuB,EACrC,KAAK,KAAO,IAAI,MAAM,KAAK,QAAS,CAChC,IAAK,CAACC,EAAQC,IAAQ,CAClB,IAAIC,EACJ,GAAKF,EAAOC,EAAI,SAAS,CAAC,EAItBC,EAAQF,EAAOC,EAAI,SAAS,CAAC,MAJJ,CACzB,IAAME,EAASF,EAAI,SAAS,EAAE,YAAY,EAC1CC,EAAQF,EAAOG,CAAM,CACzB,CAIA,MAAI,CAACD,GAASD,EAAI,SAAS,EAAE,YAAY,IAAMvB,EAAQ,YAAY,IAC/DwB,EAAQ9B,GAEL8B,CACX,CACJ,CAAC,IAILH,IAEA,KAAK,KAAO,CAER,CAACrB,CAAO,EAAGN,CACf,EAER,CAEA,SAASsB,EAAkBU,EAAY,CAYnC,OAViB,OAAO,KAAK9B,EAAI,aAAa,QAAQ,EAE7B,OAAO,CAAC+B,EAAKb,IAAW,CAC7C,IAAMc,EAAWC,EAAMH,EAAYZ,CAAM,EACzC,OAAIc,EAAWD,EAAI,WACfA,EAAI,SAAWC,EACfD,EAAI,OAASb,GAEVa,CACX,EAAG,CAAE,SAAU,IAAU,OAAQ,IAAK,CAAC,CAE3C,CAEA/B,EAAI,QAAQ,UAAW,gBAAkB,CACrC,IAAMW,EAAQ,MAAMH,EAAkB,EAGhC0B,EAAc,CAAC,EACrB,QAASC,EAAI,EAAGA,EAAIxB,EAAM,OAAQwB,IAAK,CACnC,IAAMC,EAAOzB,EAAMwB,CAAC,EAEhBE,EAAe,KACnB,GAAID,EAAK,OACLC,EAAe,CAACD,EAAK,MAAM,UACpBA,EAAK,SACZC,EAAe,CAAC,GAAGD,EAAK,QAAQ,MAEhC,OAAM,IAAI,MAAM,wCAAwCD,CAAC,EAAE,EAG/D,QAAWL,KAAcO,EAAc,CACnC,IAAMd,EAAU,CAAE,GAAGa,EAAM,OAAQN,EAAY,SAAU,MAAU,EACnE,GAAI,CAAC9B,EAAI,aAAa,SAASuB,EAAQ,MAAM,EAAG,CAC5C,IAAMJ,EAAUC,EAAkBU,CAAU,EAC5C,MAAM,IAAI,MAAM,mBAAmBA,CAAU,2BAA2BK,CAAC,mBAAmBhB,EAAQ,MAAM,IAAI,CAClH,CAEKe,EAAYJ,CAAU,IACvBI,EAAYJ,CAAU,EAAI,CAAC,GAE/BI,EAAYJ,CAAU,EAAE,KAAKP,CAAO,CACxC,CACJ,CAEA,QAAWe,KAAa,OAAO,KAAKtC,EAAI,aAAa,QAAQ,EAAG,CAC5D,IAAMW,EAAQuB,EAAYI,CAAS,GAAK,CAAC,EACnCC,EAAOvC,EAAI,aAAa,SAASsC,CAAS,EA4BhD,GAzBI,QACwB,GAyBxB,MAAM,IAAI,MAAM,4BAA4BA,CAAS,wCAAwC,EAI7FnC,GACAQ,EAAM,KAAK,CACP,KAAMb,EACN,KAAM,GACN,KAAM,GACN,OAAQ,EACZ,CAAC,EAKL0C,EAAgCD,EAAM5B,CAAK,EAU3CX,EAAI,aAAa,eAAesC,EAAW,CACvC,MAAM,KAAKG,EAAc,CAAE,MAAAC,EAAO,IAAAC,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAI,CAAC,EAAG,CAI/D,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EACxF,OAAA2C,EAAoBb,EAAK,KAAMQ,GAAU,OAAO,KAAK5C,EAAI,aAAa,SAASsC,CAAS,EAAE,MAAM,CAAC,EACjGI,EAAQ,MAAMQ,EAAgBP,EAAKP,EAAK,KAAMM,EAAOI,EAAQ,IAAI,EAE1DL,EAAa,CAAE,GAAGI,EAAU,MAAAH,EAAO,IAAAC,EAAK,OAAAC,CAAO,CAAC,CAC3D,EACA,MAAM,KAAKO,EAAc,CAAE,MAAAC,EAAO,IAAAT,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAG,CAI1D,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EAExF,GAAI,CAAC8B,EAAK,KACN,MAAM,IAAIiB,EAKd,GAHAJ,EAAoBb,EAAK,KAAMQ,CAAM,EACrCU,EAAyBlB,EAAK,KAAMgB,CAAK,EAErChB,EAAK,SACL,QAAWT,KAAO,OAAO,KAAKS,EAAK,QAAQ,EAAG,CAC1C,IAAMmB,EAAWnB,EAAK,SAAST,CAAG,EAC9B,OAAO4B,GAAa,WACpBH,EAAMzB,CAAG,EAAI,MAAM4B,EAAS,CAAE,KAAMT,EAAQ,KAAM,IAAAH,EAAK,MAAAS,CAAM,CAAC,EAE9DA,EAAMzB,CAAG,EAAImB,EAAQ,KAAKS,CAAQ,CAE1C,CAGJ,IAAMC,EAAoBJ,EAAMb,EAAK,UAAU,IAAM,OAC/CkB,EAAkB,CAAC,EAGzB,GAFAA,EAAgBlB,EAAK,UAAU,EAAI,CAAE,GAAIa,EAAMb,EAAK,UAAU,CAAE,EAE5DiB,EAAmB,CACnB,IAAMd,EAAQ,MAAMQ,EAAgBP,EAAKP,EAAK,KAAMqB,EAAiBX,EAAQ,IAAI,EAQjF,IANc,MAAMP,EAAK,KAAK,CAC1B,MAAAG,EACA,IAAAC,EACA,OAAAC,CACJ,CAAC,GAES,SAAW,EACjB,MAAM,IAAIS,EAGd,OAAOF,EAAa,CAAE,MAAAC,EAAO,IAAAT,EAAK,OAAAC,EAAQ,GAAGC,CAAS,CAAC,CAC3D,CAEA,OAAOM,EAAa,CAAE,MAAAC,EAAO,IAAAT,EAAK,OAAAC,EAAQ,GAAGC,CAAS,CAAC,CAC3D,EAEA,MAAM,OAAOa,EAAgB,CAAE,OAAAC,EAAQ,IAAAhB,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAG,CAI/D,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EAExF,GAAI,CAAC8B,EAAK,KACN,MAAM,IAAIiB,EAOd,GAJAJ,EAAoBb,EAAK,KAAMQ,CAAM,EACrCU,EAAyBlB,EAAK,KAAMuB,CAAM,EAGtCvB,EAAK,SACL,QAAWgB,KAASO,EAChB,QAAWhC,KAAO,OAAO,KAAKS,EAAK,QAAQ,EAAG,CAC1C,IAAMmB,EAAWnB,EAAK,SAAST,CAAG,EAC9B,OAAO4B,GAAa,WACpBH,EAAMzB,CAAG,EAAI,MAAM4B,EAAS,CAAE,KAAMT,EAAQ,KAAM,IAAAH,EAAK,MAAAS,CAAM,CAAC,EAE9DA,EAAMzB,CAAG,EAAImB,EAAQ,KAAKS,CAAQ,CAE1C,CAIR,OAAOG,EAAe,CAAE,OAAAC,EAAQ,IAAAhB,EAAK,OAAAC,EAAQ,GAAGC,CAAS,CAAC,CAC9D,EAEA,MAAM,OAAOe,EAAgB,CAAE,MAAAlB,EAAO,IAAAC,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAG,CAI9D,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EAExF,OAAAoC,EAAQ,MAAMQ,EAAgBP,EAAKP,EAAK,OAAQM,EAAOI,EAAQ,IAAI,EAE5Dc,EAAe,CAAE,MAAAlB,EAAO,IAAAC,EAAK,OAAAC,EAAQ,GAAGC,CAAS,CAAC,CAC7D,EAEA,MAAM,WAAWgB,EAAoB,CAAE,MAAAnB,EAAO,IAAAC,EAAK,OAAAC,EAAQ,GAAGC,CAAS,EAAG,CAItE,IAAMC,EAAUC,EAAsBJ,CAAG,EACnCP,EAAO,MAAMY,EAAuBL,EAAKhC,EAAOP,EAASG,EAAeD,CAAU,EAExF,OAAAoC,EAAQ,MAAMQ,EAAgBP,EAAKP,EAAK,WAAYM,EAAOI,EAAQ,IAAI,EAEhEe,EAAmB,CAAE,GAAGhB,EAAU,MAAAH,EAAO,IAAAC,EAAK,OAAAC,CAAO,CAAC,CACjE,CACJ,CAAC,CACL,CACJ,CAAC,CACL,CAEA,eAAeM,EAAgBP,EAA0BP,EAAMM,EAAOoB,EAAM,CACxE,GAAI,CAAC1B,EACD,MAAM,IAAIiB,EAEd,IAAMP,EAAUC,EAAsBJ,CAAG,EAIzC,GAFAD,EAAQA,GAAS,CAAC,EAEd,OAAON,GAAS,SAAU,CAC1B,GAAM,CAAE,OAAA2B,CAAO,EAAI3B,EAGnB,GAAI2B,EACA,QAAWpC,KAAO,OAAO,KAAKoC,CAAM,EAAG,CACnC,IAAMC,EAAUD,EAAOpC,CAAG,EAC1B,GAAI,OAAOqC,GAAY,SAEnBtB,EAAMf,CAAG,EAAI,CACT,GAAImB,EAAQ,KAAKkB,CAAO,CAC5B,MAMA,SAAWC,KAAa,OAAO,KAAKD,CAAO,EAAG,CAC1C,IAAME,EAASF,EAAQC,CAAS,EAChCvB,EAAMf,CAAG,EAAI,CACT,CAACsC,CAAS,EAAGnB,EAAQ,KAAKoB,CAAM,CACpC,CACJ,CAER,CAER,MAAW,OAAO9B,GAAS,aACvBM,EAAQ,MAAMN,EAAK,CAAE,KAAA0B,EAAM,IAAAnB,EAAK,MAAAD,CAAM,CAAC,GAE3C,OAAOA,CACX,CAEA,eAAeM,EAAuBL,EAA0BhC,EAA2BP,EAAiBG,EAAuBD,EAAa,GAAO,CACnJ,IAAMwC,EAAUC,EAAsBJ,CAAG,EACzC,MAAMG,EAAQ,yBAAyB,EACvC,IAAMpC,EAAQyD,EAASrB,EAAS1C,EAASG,EAAeD,CAAU,EAC5D8B,EAAOgC,EAASzD,EAAOD,CAAK,EAClC,GAAI,CAAC0B,EACD,MAAAO,EAAI,MAAM,QAAQ,IAAI,KAAK,CAAE,MAAAjC,EAAO,MAAAC,CAAM,EAAG,mBAAmB,EAC1D,IAAI0C,EAEd,OAAAV,EAAI,MAAM,QAAQ,IAAI,MAAM,CAAE,MAAAjC,EAAO,KAAA0B,CAAK,EAAG,YAAY,EAClDA,CACX,CAEA,SAASa,EAAoBb,EAAMQ,EAAQ,CACvC,GAAI,CAACR,EACD,MAAM,IAAIiB,EAEd,GAAM,CAAE,OAAQgB,CAAe,EAAIjC,EAEnC,GAAIiC,GACA,QAAWC,KAAS1B,EAChB,GAAI,CAACyB,EAAe,SAASC,CAAK,EAC9B,MAAM,IAAIC,EAAkBD,CAAK,EAIjD,CAEA,IAAME,EAAiB,CAACb,EAAQU,IAAmB,CAC/C,QAAWjB,KAASO,EAAQ,CACxB,IAAMc,EAAc,OAAO,KAAKrB,CAAK,EACrC,QAAWsB,KAAcD,EACrB,GAAI,CAACJ,EAAe,SAASK,CAAU,EACnC,MAAM,IAAIH,EAAkBG,CAAU,CAGlD,CACJ,EAEA,SAASpB,EAAyBlB,EAAMuB,EAAQ,CAC5C,GAAM,CAAE,OAAQU,CAAe,EAAIjC,EAE/BiC,IACK,MAAM,QAAQV,CAAM,EAKrBa,EAAeb,EAAQU,CAAc,EAHrCG,EAAe,CAACb,CAAM,EAAGU,CAAc,EAMnD,CAEA,SAAS7B,EAAgCD,EAAc5B,EAAO,CAE1D,IAAMgE,EACF,OAAO,OAAOpC,EAAK,MAAM,EACpB,OAAOqC,GAAM,CAACA,EAAE,YAAc,CAACA,EAAE,UAAW,EAC5C,IAAI,CAAC,CAAE,UAAAC,CAAU,IAAOA,CAAU,EAE3C,QAAWzC,KAAQzB,EAAO,CACtB,GAAM,CAAE,OAAAO,EAAQ,KAAA4D,CAAK,EAAI1C,EACzB,GAAI0C,GAAQA,EAAK,OAAQ,CACrB,IAAMlC,EAASkC,EAAK,OACpB,QAAWC,KAAUJ,EACjB,GAAI,CAAC/B,EAAO,SAASmC,CAAM,EACvB,MAAM,IAAIC,EAAwBD,EAAQ7D,CAAM,CAG5D,CACJ,CACJ,CAEA,IAAO+D,GAAQC,EAAGnF,CAAI","names":["fp","leven","fastifyUser","findRule","rules","roles","found","rule","role","find_rule_default","getRequestFromContext","ctx","getRoles","request","roleKey","anonymousRole","isRolePath","output","user","rolesRaw","roleKeys","key","createError","ERROR_PREFIX","Unauthorized","UnauthorizedField","MissingNotNullableError","fastifyLogto","PLT_ADMIN_ROLE","auth","app","opts","fastifyUser","adminSecret","roleKey","userKey","isRolePath","anonymousRole","composeLogToRules","rolesResp","roles","rules","role","scopesResp","scopes","scope","roleName","scopeAction","entity","nearest","findNearestEntity","checkExists","r","newRule","setupUser","forceAdminRole","target","key","value","newKey","ruleEntity","acc","distance","leven","entityRules","i","rule","ruleEntities","entityKey","type","checkSaveMandatoryFieldsInRules","originalFind","where","ctx","fields","restOpts","request","getRequestFromContext","findRuleForRequestUser","checkFieldsFromRule","fromRuleToWhere","originalSave","input","Unauthorized","checkInputFromRuleFields","defaults","hasAllPrimaryKeys","whereConditions","originalInsert","inputs","originalDelete","originalUpdateMany","user","checks","clauses","clauseKey","clause","getRoles","find_rule_default","fieldsFromRule","field","UnauthorizedField","validateInputs","inputFields","inputField","mandatoryFields","k","camelcase","save","mField","MissingNotNullableError","index_default","fp"]}
package/package.json CHANGED
@@ -1,21 +1,34 @@
1
1
  {
2
2
  "name": "@albirex/platformatic-logto",
3
- "version": "1.2.5",
3
+ "version": "1.3.2",
4
4
  "description": "LogTo roles and scopes integrated in Platformatic authorization",
5
5
  "type": "module",
6
- "main": "lib/index.js",
7
- "types": "lib/index.d.ts",
6
+ "main": "./lib/index.cjs",
7
+ "module": "./lib/index.js",
8
+ "types": "./lib/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./lib/index.d.ts",
13
+ "default": "./lib/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./lib/index.d.cts",
17
+ "default": "./lib/index.cjs"
18
+ }
19
+ }
20
+ },
8
21
  "files": [
9
- "/lib"
22
+ "lib"
10
23
  ],
11
24
  "repository": {
12
25
  "type": "git",
13
26
  "url": "https://github.com/albiper/platformatic-logto"
14
27
  },
15
28
  "peerDependencies": {
29
+ "@platformatic/db": "^2.66.0",
16
30
  "fastify": "^5.0.0",
17
- "platformatic": ">=2",
18
- "@platformatic/db": "^2.66.0"
31
+ "platformatic": ">=2"
19
32
  },
20
33
  "devDependencies": {
21
34
  "@eslint/js": "^9.12.0",
@@ -24,11 +37,12 @@
24
37
  "dotenv": "^16.5.0",
25
38
  "eslint": "^9.12.0",
26
39
  "tap": "^21.1.0",
40
+ "tsup": "^8.5.0",
27
41
  "typescript": "~5.5",
28
42
  "typescript-eslint": "^8.16.0"
29
43
  },
30
44
  "dependencies": {
31
- "@albirex/fastify-logto": "^1.2.3",
45
+ "@albirex/fastify-logto": "^1.3.1",
32
46
  "@fastify/error": "^4.1.0",
33
47
  "@logto/js": "^5.1.1",
34
48
  "@logto/node": "^3.1.4",
@@ -44,9 +58,9 @@
44
58
  "author": "",
45
59
  "license": "ISC",
46
60
  "scripts": {
47
- "build": "npm run lint && npm run build-ts",
48
61
  "build-ts": "tsc",
49
62
  "lint": "eslint",
50
- "test": "tap --tsconfig=tsconfig.test.json --disable-coverage --jobs=1 test/**/*.test.ts"
63
+ "test": "tap --tsconfig=tsconfig.test.json --disable-coverage --jobs=1 test/**/*.test.ts",
64
+ "build": "npm run lint && tsup"
51
65
  }
52
66
  }
@@ -1,12 +0,0 @@
1
- import createError from '@fastify/error';
2
- export declare const Unauthorized: createError.FastifyErrorConstructor<{
3
- code: "PLT_DB_AUTH_UNAUTHORIZED";
4
- statusCode: 401;
5
- }, [any?, any?, any?]>;
6
- export declare const UnauthorizedField: createError.FastifyErrorConstructor<{
7
- code: "PLT_DB_AUTH_FIELD_UNAUTHORIZED";
8
- statusCode: 401;
9
- }, [any?, any?, any?]>;
10
- export declare const MissingNotNullableError: createError.FastifyErrorConstructor<{
11
- code: "PLT_DB_AUTH_NOT_NULLABLE_MISSING";
12
- }, [any?, any?, any?]>;
@@ -1,6 +0,0 @@
1
- 'use strict';
2
- import createError from '@fastify/error';
3
- const ERROR_PREFIX = 'PLT_DB_AUTH';
4
- export const Unauthorized = createError(`${ERROR_PREFIX}_UNAUTHORIZED`, 'operation not allowed', 401);
5
- export const UnauthorizedField = createError(`${ERROR_PREFIX}_FIELD_UNAUTHORIZED`, 'field not allowed: %s', 401);
6
- export const MissingNotNullableError = createError(`${ERROR_PREFIX}_NOT_NULLABLE_MISSING`, 'missing not nullable field: "%s" in save rule for entity "%s"');
@@ -1,3 +0,0 @@
1
- import { PlatformaticRule } from '../index.js';
2
- declare function findRule(rules: PlatformaticRule[], roles: string[]): any;
3
- export default findRule;
@@ -1,17 +0,0 @@
1
- 'use strict';
2
- function findRule(rules, roles) {
3
- let found = null;
4
- for (const rule of rules) {
5
- for (const role of roles) {
6
- if (rule.role === role) {
7
- found = rule;
8
- break;
9
- }
10
- }
11
- if (found) {
12
- break;
13
- }
14
- }
15
- return found;
16
- }
17
- export default findRule;
@@ -1,2 +0,0 @@
1
- export declare function getRequestFromContext(ctx: any): any;
2
- export declare function getRoles(request: any, roleKey: any, anonymousRole: any, isRolePath?: boolean): any[];
@@ -1,36 +0,0 @@
1
- 'use strict';
2
- export function getRequestFromContext(ctx) {
3
- if (ctx && !ctx.reply) {
4
- throw new Error('Missing reply in context. You should call this function with { ctx: { reply }}');
5
- }
6
- return ctx.reply.request;
7
- }
8
- export function getRoles(request, roleKey, anonymousRole, isRolePath = false) {
9
- let output = [];
10
- const user = request.user;
11
- if (!user) {
12
- output.push(anonymousRole);
13
- return output;
14
- }
15
- let rolesRaw;
16
- if (isRolePath) {
17
- const roleKeys = roleKey.split('.');
18
- rolesRaw = user;
19
- for (const key of roleKeys) {
20
- rolesRaw = rolesRaw[key];
21
- }
22
- }
23
- else {
24
- rolesRaw = user[roleKey];
25
- }
26
- if (typeof rolesRaw === 'string') {
27
- output = rolesRaw.split(',');
28
- }
29
- else if (Array.isArray(rolesRaw)) {
30
- output = rolesRaw;
31
- }
32
- if (output.length === 0) {
33
- output.push(anonymousRole);
34
- }
35
- return output;
36
- }