ldap-rest 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/index.js CHANGED
@@ -1 +1 @@
1
- import{fileURLToPath as e}from"url";import{join as t,dirname as r}from"path";import i from"express";import s from"body-parser";import{parseConfig as o}from"../lib/parseConfig.js";import{keepRawBody as n}from"../lib/rawBody.js";import a from"../config/args.js";import h from"../lib/ldapActions.js";import{buildLogger as l}from"../logger/winston.js";import{setLogger as u}from"../lib/expressFormatedResponses.js";import{prefixCoversPath as d}from"../lib/auth/base.js";import{assertAuthzComposition as g,assertEnforceable as c,LDAP_JUDGING_HOOKS as p}from"../lib/authz/composition.js";import{errorBody as f,AUTHZ_REFUSED as m}from"../lib/errors.js";export{BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UriTooLongError}from"../lib/errors.js";import{registerAttributeTypes as w,recordHookOwner as b}from"../lib/utils.js";export{asyncHandler,escapeDnValue,escapeLdapFilter,escapeRegex,getCompiledRegex,getParentDn,getRdn,hookOwner,isAuthzRefusal,isChildOf,isDnInBranch,isDnSyntax,isDummyMemberDn,knownAttributeName,launchHooks,launchHooksChained,movedToTrash,normalizeDn,organizationLink,parseDn,rdnTypes,rdnValue,substringSearchFilter,transformSchemas,unescapeDnValue,validateDnValue}from"../lib/utils.js";import{runForRequest as $}from"../lib/requestContext.js";import y from"../plugins/priority.json.js";const P=p.filter(e=>"ldapsearchfilter"!==e);class DM{constructor(){this.hooks={},this.loadedPlugins={},this.authenticators=[],this._authState={dispatcherMounted:!1},this._errorMiddlewareSetup=!1,this.bodyParsers=i.Router(),this._composed=!1,this.pluginLoads=new Map,this.authzHooks=new WeakSet,this.config=o(a),this.app=i(),this.app.use(this.bodyParsers),this.app.use(s.json({verify:n})),this.app.use(s.urlencoded({extended:!0,verify:n})),this.logger=l(this.config),this.ldap=new h(this),u(this.logger),this.operationSequence=0;const e=[];if(this.config.plugin){const t=this.config.plugin.find(e=>e.includes("configApi"));let r=this.config.plugin.filter(e=>!e.includes("configApi"));const i=this.logger,s=(async()=>{for(const e of y){const t=this.resolveModule(DM.moduleOf(e)),s=r.filter(e=>this.resolveModule(DM.moduleOf(e))===t);if(0!==s.length){r=r.filter(e=>!s.includes(e));for(const e of s)i.debug(`Loading ${e} in the priority pass`),await this.loadPlugin(e)}}})();e.push(s),e.push(s.then(async()=>{const e=r.map(e=>(i.debug(`Loading ${e} in the parallel batch`),this.loadPlugin(e)));await Promise.all(e)})),t&&e.push(Promise.all(e).then(()=>this.loadPlugin(t)))}this.ready=new Promise((t,r)=>{e.length>0?Promise.all(e).then(async()=>{this.setupErrorMiddleware(),this.warnUnauthenticatedRoutes(),await g(this),c(this),await this.learnAttributeTypes(),this._composed=!0,this.afterLoad(),t()}).catch(e=>r(new Error("Error loading plugins: "+e))):(this.setupErrorMiddleware(),this._composed=!0,t())})}async learnAttributeTypes(){if(Object.values(this.loadedPlugins).some(e=>e.roles?.includes("authz")))try{w((await this.ldap.schemaIndex()).schema.attributeTypes)}catch(e){this.logger.warn(`Could not read the directory schema, DNs are compared with the usual attribute type names only: ${String(e)}`)}}routerStack(){const e=this.app;return(e.router??e._router)?.stack}withConfig(e){return Object.assign(Object.create(Object.getPrototypeOf(this)),this,{config:e})}mountAuthDispatcher(e){const t=e.roles||[];t.includes("protect")||t.includes("logging")||this.mountAuthDispatcherNow()}mountAuthDispatcherNow(){this._authState.dispatcherMounted||(this._authState.dispatcherMounted=!0,this.app.use((e,t,r)=>{this.dispatchAuth(e,t,r)}))}registerAuthenticator(e){this.mountAuthDispatcherNow(),this.authenticators.includes(e)||this.authenticators.push(e)}registerBodyParser(e,t){this.bodyParsers.use(e,t)}dispatchAuth(e,t,r){let i=-1,s=[];for(const t of this.authenticators)for(const r of t.pathPrefixes)d(r,e.path)&&(r.length>i?(i=r.length,s=[t]):r.length!==i||s.includes(t)||s.push(t));if(i<0&&(s=this.authenticators.filter(e=>0===e.pathPrefixes.length)),0===s.length)return r();const finish=i=>{if(i>=s.length)return $(e,r);const o=s[i];if(!o.afterChain)return finish(i+1);o.afterChain(e,t,()=>finish(i+1))},o=[];e.authenticators=o;const run=r=>{if(r>=s.length)return finish(0);const i=s[r];i.authenticate(e,t,()=>{i.vouchedFor(e)&&o.push(i.name),run(r+1)})};run(0)}afterLoad(){for(const e of Object.values(this.loadedPlugins))try{e.afterLoad?.()}catch(t){this.logger.error(`Plugin ${e.name}: afterLoad failed: ${String(t)}`)}}warnUnauthenticatedRoutes(){const e=Object.values(this.loadedPlugins).filter(e=>e.roles?.includes("auth"));if(0===e.length)return[];const t=[];for(const r of e){if(!r.pathPrefixes||0===r.pathPrefixes.length)return[];t.push(...r.pathPrefixes)}const r=this.routerStack(),i=new Set;for(const e of r||[]){const t=e.route?.path;"string"==typeof t?i.add(t):Array.isArray(t)&&t.forEach(e=>"string"==typeof e&&i.add(e))}const s=[...i].filter(e=>!t.some(t=>e===t||e.startsWith(`${t}/`))),o=Object.values(this.loadedPlugins).some(e=>e.roles?.includes("authz"));return s.length>0&&this.logger.warn(`Authentication is restricted to ${t.join(", ")}, so these routes are served without authentication`+(o?" nor authorization":"")+`: ${s.sort().join(", ")}`),s.sort()}setupErrorMiddleware(){if(this._errorMiddlewareSetup&&this._errorMiddleware){const e=this.routerStack();if(e){const t=e.findIndex(e=>e.handle===this._errorMiddleware);-1!==t&&e.splice(t,1)}}this._errorMiddleware=(e,t,r,i)=>{let s="statusCode"in e?e.statusCode:500,o=e.message;if(500===s&&/\[authz-forbidden\]/.test(e.message)?(s=403,o=m):403===s&&/\[authz-forbidden\]/.test(e.message)&&(o=m),s>=400&&s<500)return this.logger.warn(`Client error ${s} in request ${t.method} ${t.path}: ${o}`),void(r.headersSent||r.status(s).json(f(e,o)));this.logger.error(`Server error ${s} in request ${t.method} ${t.path}: ${e.message}`,{stack:e.stack,url:t.url,method:t.method}),r.headersSent||r.status(s).json({error:"Internal Server Error",message:this.config.debug?e.message:"An error occurred"})},this.app.use(this._errorMiddleware),this._errorMiddlewareSetup=!0}run(){return process.on("uncaughtException",e=>{this.logger.error(`Uncaught exception: ${e.message}`,{stack:e.stack})}),process.on("unhandledRejection",(e,t)=>{this.logger.error(`Unhandled promise rejection: ${e}`,{reason:e,promise:t})}),new Promise((e,t)=>{this.server=this.app.listen(this.config.port,r=>{r?(this.logger.error(`Error starting server: ${r}`),t(r)):(this.logger.debug(`Server started on port ${this.config.port}`),e())})})}stop(){this.app.removeAllListeners(),this.server?.close(),this.logger.debug("Server stopped")}static moduleOf(e){const t=e.indexOf(":");return-1===t?e:e.substring(0,t)}resolveModule(i){return i.startsWith("core/")?i.replace("core/",t(r(e(import.meta.url)),"..","plugins")+"/").replace(/$/,".js"):i.startsWith(".")?e(new URL(i,import.meta.url)):i}loadPlugin(e){let t,r;if(/:/.test(e)){let i=e.substring(e.indexOf(":")+1);if(e=e.substring(0,e.indexOf(":")),/:/.test(i)){t=i.substring(0,i.indexOf(":")),t||(t=void 0),i=i.substring(i.indexOf(":")+1);try{r=JSON.parse(i),"object"!=typeof r?(this.logger.error(`Overrides for plugin ${e} are not valid: ${i}`),r=void 0):this.logger.debug(`Overrides for plugin ${t||e}: ${i}`)}catch(t){this.logger.error(`Failed to parse overrides for plugin ${e}: ${t}, using ${i}`),r=void 0}}else t=i,t||(t=void 0)}else t=void 0;const i=r?void 0:`${this.resolveModule(e)}\0${t??""}`,s=void 0===i?void 0:this.pluginLoads.get(i);if(s)return this.logger.debug(`Plugin ${e} already loaded, skipping`),s;const o=this.importPlugin(e,t,r);if(void 0!==i){this.pluginLoads.set(i,o);const forget=()=>{this.pluginLoads.get(i)===o&&this.pluginLoads.delete(i)};o.then(e=>e||forget(),forget)}return o}importPlugin(e,t,r){return this.logger.debug(`Loading plugin ${e}`),e.startsWith("core/")&&(e=this.resolveModule(e)),new Promise((i,s)=>{import(e).then(async o=>{let n;if(o&&o.default&&(o=o.default),r){const e={...this.config,...r};n=new o(this.withConfig(e))}else n=new o(this);if(!n)return s(new Error(`Unable to load ${e}`));i(await this.registerPlugin(e,n,t)),this.logger.debug(`Plugin ${n.name} loaded`)}).catch(t=>s(new Error(`Failed to load plugin ${e}: ${t}`)))})}async admitToComposition(e){this.loadedPlugins[e.name]=e;try{await g(this,e)}finally{delete this.loadedPlugins[e.name]}}async registerPlugin(e,t,r){if(t.name||(t.name=e),r&&(t.name=r),this.loadedPlugins[t.name])return this.logger.warn(`Plugin ${e} not registered: the name "${t.name}" is already taken. This instance is dropped and its routes will not exist. To load the same plugin twice, give each one a name: --plugin '${e}:${t.name}2:{…}'`),!1;if(this.logger.debug(`Registering plugin ${e} as ${t.name}`),t.dependencies)for(const e in t.dependencies)this.loadedPlugins[e]||(this.logger.debug(`Plugin ${t.name} depends on ${e}, loading it first`),await this.loadPlugin(t.dependencies[e]));if(this._composed&&await this.admitToComposition(t),t.api&&(this.mountAuthDispatcher(t),this.logger.debug(`Plugin ${t.name} has API, registering it`),await t.api(this.app),this._errorMiddlewareSetup&&this.setupErrorMiddleware()),t.hooks)for(const e in t.hooks){this.logger.debug(`Plugin ${t.name} has hook ${e}, registering it`);const r=t.hooks[e];if(this.hooks[e]||(this.hooks[e]=[]),!r||"function"!=typeof r)throw new Error(`Plugin ${t.name}: hook ${e} is invalid`);{const i=this.hooks[e];if(t.roles?.includes("authz")&&P.includes(e)){const e=i.findIndex(e=>!this.authzHooks.has(e));i.splice(-1===e?i.length:e,0,r),this.authzHooks.add(r)}else i.push(r);b(r,t.name,e)}}return this.loadedPlugins[t.name]=t,!0}}export{DM,b as recordHookOwner,w as registerAttributeTypes};
1
+ import{fileURLToPath as e}from"url";import{join as t,dirname as r}from"path";import s from"express";import i from"body-parser";import{parseConfig as o}from"../lib/parseConfig.js";import{keepRawBody as n}from"../lib/rawBody.js";import a from"../config/args.js";import h from"../lib/ldapActions.js";import{buildLogger as l}from"../logger/winston.js";import{setLogger as u}from"../lib/expressFormatedResponses.js";import{prefixCoversPath as d}from"../lib/auth/base.js";import{assertAuthzComposition as g,assertEnforceable as p,LDAP_JUDGING_HOOKS as c}from"../lib/authz/composition.js";import{errorBody as f,AUTHZ_REFUSED as m}from"../lib/errors.js";export{BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UriTooLongError}from"../lib/errors.js";import{registerAttributeTypes as w,recordHookOwner as b}from"../lib/utils.js";export{asyncHandler,escapeDnValue,escapeLdapFilter,escapeRegex,getCompiledRegex,getParentDn,getRdn,hookOwner,isAuthzRefusal,isChildOf,isDnInBranch,isDnSyntax,isDummyMemberDn,knownAttributeName,launchHooks,launchHooksChained,movedToTrash,normalizeDn,organizationLink,parseDn,rdnTypes,rdnValue,substringSearchFilter,transformSchemas,unescapeDnValue,validateDnValue}from"../lib/utils.js";import{runForRequest as $}from"../lib/requestContext.js";import y from"../plugins/priority.json.js";const P=c.filter(e=>"ldapsearchfilter"!==e);class DM{constructor(){this.hooks={},this.loadedPlugins={},this.authenticators=[],this._authState={dispatcherMounted:!1},this._errorMiddlewareSetup=!1,this.bodyParsers=s.Router(),this._composed=!1,this.pluginLoads=new Map,this.authzHooks=new WeakSet,this.config=o(a),this.app=s(),this.app.disable("x-powered-by"),this.app.use((e,t,r)=>{t.setHeader("X-Content-Type-Options","nosniff"),r()}),this.app.use(this.bodyParsers),this.app.use(i.json({verify:n})),this.app.use(i.urlencoded({extended:!0,verify:n})),this.logger=l(this.config),this.ldap=new h(this),u(this.logger),this.operationSequence=0;const e=[];if(this.config.plugin){const t=this.config.plugin.find(e=>e.includes("configApi"));let r=this.config.plugin.filter(e=>!e.includes("configApi"));const s=this.logger,i=(async()=>{for(const e of y){const t=this.resolveModule(DM.moduleOf(e)),i=r.filter(e=>this.resolveModule(DM.moduleOf(e))===t);if(0!==i.length){r=r.filter(e=>!i.includes(e));for(const e of i)s.debug(`Loading ${e} in the priority pass`),await this.loadPlugin(e)}}})();e.push(i),e.push(i.then(async()=>{const e=r.map(e=>(s.debug(`Loading ${e} in the parallel batch`),this.loadPlugin(e)));await Promise.all(e)})),t&&e.push(Promise.all(e).then(()=>this.loadPlugin(t)))}this.ready=new Promise((t,r)=>{e.length>0?Promise.all(e).then(async()=>{this.setupErrorMiddleware(),this.warnUnauthenticatedRoutes(),await g(this),p(this),await this.learnAttributeTypes(),this._composed=!0,this.afterLoad(),t()}).catch(e=>r(new Error("Error loading plugins: "+e))):(this.setupErrorMiddleware(),this._composed=!0,t())})}async learnAttributeTypes(){if(Object.values(this.loadedPlugins).some(e=>e.roles?.includes("authz")))try{w((await this.ldap.schemaIndex()).schema.attributeTypes)}catch(e){this.logger.warn(`Could not read the directory schema, DNs are compared with the usual attribute type names only: ${String(e)}`)}}routerStack(){const e=this.app;return(e.router??e._router)?.stack}withConfig(e){return Object.assign(Object.create(Object.getPrototypeOf(this)),this,{config:e})}mountAuthDispatcher(e){const t=e.roles||[];t.includes("protect")||t.includes("logging")||this.mountAuthDispatcherNow()}mountAuthDispatcherNow(){this._authState.dispatcherMounted||(this._authState.dispatcherMounted=!0,this.app.use((e,t,r)=>{this.dispatchAuth(e,t,r)}))}registerAuthenticator(e){this.mountAuthDispatcherNow(),this.authenticators.includes(e)||this.authenticators.push(e)}registerBodyParser(e,t){this.bodyParsers.use(e,t)}dispatchAuth(e,t,r){let s=-1,i=[];for(const t of this.authenticators)for(const r of t.pathPrefixes)d(r,e.path)&&(r.length>s?(s=r.length,i=[t]):r.length!==s||i.includes(t)||i.push(t));if(s<0&&(i=this.authenticators.filter(e=>0===e.pathPrefixes.length)),0===i.length)return r();const finish=s=>{if(s>=i.length)return $(e,r);const o=i[s];if(!o.afterChain)return finish(s+1);o.afterChain(e,t,()=>finish(s+1))},o=[];e.authenticators=o;const run=r=>{if(r>=i.length)return finish(0);const s=i[r];s.authenticate(e,t,()=>{s.vouchedFor(e)&&o.push(s.name),run(r+1)})};run(0)}afterLoad(){for(const e of Object.values(this.loadedPlugins))try{e.afterLoad?.()}catch(t){this.logger.error(`Plugin ${e.name}: afterLoad failed: ${String(t)}`)}}warnUnauthenticatedRoutes(){const e=Object.values(this.loadedPlugins).filter(e=>e.roles?.includes("auth"));if(0===e.length)return[];const t=[];for(const r of e){if(!r.pathPrefixes||0===r.pathPrefixes.length)return[];t.push(...r.pathPrefixes)}const r=this.routerStack(),s=new Set;for(const e of r||[]){const t=e.route?.path;"string"==typeof t?s.add(t):Array.isArray(t)&&t.forEach(e=>"string"==typeof e&&s.add(e))}const i=[...s].filter(e=>!t.some(t=>e===t||e.startsWith(`${t}/`))),o=Object.values(this.loadedPlugins).some(e=>e.roles?.includes("authz"));return i.length>0&&this.logger.warn(`Authentication is restricted to ${t.join(", ")}, so these routes are served without authentication`+(o?" nor authorization":"")+`: ${i.sort().join(", ")}`),i.sort()}setupErrorMiddleware(){if(this._errorMiddlewareSetup&&this._errorMiddleware){const e=this.routerStack();if(e){const t=e.findIndex(e=>e.handle===this._errorMiddleware);-1!==t&&e.splice(t,1)}}this._errorMiddleware=(e,t,r,s)=>{let i="statusCode"in e?e.statusCode:500,o=e.message;if(500===i&&/\[authz-forbidden\]/.test(e.message)?(i=403,o=m):403===i&&/\[authz-forbidden\]/.test(e.message)&&(o=m),i>=400&&i<500)return this.logger.warn(`Client error ${i} in request ${t.method} ${t.path}: ${o}`),void(r.headersSent||r.status(i).json(f(e,o)));this.logger.error(`Server error ${i} in request ${t.method} ${t.path}: ${e.message}`,{stack:e.stack,url:t.url,method:t.method}),r.headersSent||r.status(i).json({error:"Internal Server Error",message:this.config.debug?e.message:"An error occurred"})},this.app.use(this._errorMiddleware),this._errorMiddlewareSetup=!0}run(){return process.on("uncaughtException",e=>{this.logger.error(`Uncaught exception: ${e.message}`,{stack:e.stack})}),process.on("unhandledRejection",(e,t)=>{this.logger.error(`Unhandled promise rejection: ${e}`,{reason:e,promise:t})}),new Promise((e,t)=>{this.server=this.app.listen(this.config.port,r=>{r?(this.logger.error(`Error starting server: ${r}`),t(r)):(this.logger.debug(`Server started on port ${this.config.port}`),e())})})}stop(){this.app.removeAllListeners(),this.server?.close(),this.logger.debug("Server stopped")}static moduleOf(e){const t=e.indexOf(":");return-1===t?e:e.substring(0,t)}resolveModule(s){return s.startsWith("core/")?s.replace("core/",t(r(e(import.meta.url)),"..","plugins")+"/").replace(/$/,".js"):s.startsWith(".")?e(new URL(s,import.meta.url)):s}loadPlugin(e){let t,r;if(/:/.test(e)){let s=e.substring(e.indexOf(":")+1);if(e=e.substring(0,e.indexOf(":")),/:/.test(s)){t=s.substring(0,s.indexOf(":")),t||(t=void 0),s=s.substring(s.indexOf(":")+1);try{r=JSON.parse(s),"object"!=typeof r?(this.logger.error(`Overrides for plugin ${e} are not valid: ${s}`),r=void 0):this.logger.debug(`Overrides for plugin ${t||e}: ${s}`)}catch(t){this.logger.error(`Failed to parse overrides for plugin ${e}: ${t}, using ${s}`),r=void 0}}else t=s,t||(t=void 0)}else t=void 0;const s=r?void 0:`${this.resolveModule(e)}\0${t??""}`,i=void 0===s?void 0:this.pluginLoads.get(s);if(i)return this.logger.debug(`Plugin ${e} already loaded, skipping`),i;const o=this.importPlugin(e,t,r);if(void 0!==s){this.pluginLoads.set(s,o);const forget=()=>{this.pluginLoads.get(s)===o&&this.pluginLoads.delete(s)};o.then(e=>e||forget(),forget)}return o}importPlugin(e,t,r){return this.logger.debug(`Loading plugin ${e}`),e.startsWith("core/")&&(e=this.resolveModule(e)),new Promise((s,i)=>{import(e).then(async o=>{let n;if(o&&o.default&&(o=o.default),r){const e={...this.config,...r};n=new o(this.withConfig(e))}else n=new o(this);if(!n)return i(new Error(`Unable to load ${e}`));s(await this.registerPlugin(e,n,t)),this.logger.debug(`Plugin ${n.name} loaded`)}).catch(t=>i(new Error(`Failed to load plugin ${e}: ${t}`)))})}async admitToComposition(e){this.loadedPlugins[e.name]=e;try{await g(this,e)}finally{delete this.loadedPlugins[e.name]}}async registerPlugin(e,t,r){if(t.name||(t.name=e),r&&(t.name=r),this.loadedPlugins[t.name])return this.logger.warn(`Plugin ${e} not registered: the name "${t.name}" is already taken. This instance is dropped and its routes will not exist. To load the same plugin twice, give each one a name: --plugin '${e}:${t.name}2:{…}'`),!1;if(this.logger.debug(`Registering plugin ${e} as ${t.name}`),t.dependencies)for(const e in t.dependencies)this.loadedPlugins[e]||(this.logger.debug(`Plugin ${t.name} depends on ${e}, loading it first`),await this.loadPlugin(t.dependencies[e]));if(this._composed&&await this.admitToComposition(t),t.api&&(this.mountAuthDispatcher(t),this.logger.debug(`Plugin ${t.name} has API, registering it`),await t.api(this.app),this._errorMiddlewareSetup&&this.setupErrorMiddleware()),t.hooks)for(const e in t.hooks){this.logger.debug(`Plugin ${t.name} has hook ${e}, registering it`);const r=t.hooks[e];if(this.hooks[e]||(this.hooks[e]=[]),!r||"function"!=typeof r)throw new Error(`Plugin ${t.name}: hook ${e} is invalid`);{const s=this.hooks[e];if(t.roles?.includes("authz")&&P.includes(e)){const e=s.findIndex(e=>!this.authzHooks.has(e));s.splice(-1===e?s.length:e,0,r),this.authzHooks.add(r)}else s.push(r);b(r,t.name,e)}}return this.loadedPlugins[t.name]=t,!0}}export{DM,b as recordHookOwner,w as registerAttributeTypes};
@@ -1 +1 @@
1
- import{fileURLToPath as e}from"url";import{join as _,dirname as a}from"path";const t=[["--port","DM_PORT",8081,"number"],["--plugin","DM_PLUGINS",[],"array","--plugins","identifiers"],["--log-level","DM_LOG_LEVEL","notice"],["--logger","DM_LOGGER","console"],["--api-prefix","DM_API_PREFIX","/api"],["--mail-domain","DM_MAIL_DOMAIN",[],"array","--mail-domains","identifiers"],["--ldap-base","DM_LDAP_BASE",""],["--ldap-dn","DM_LDAP_DN","cn=admin,dc=example,dc=com"],["--ldap-pwd","DM_LDAP_PWD","admin"],["--ldap-url","DM_LDAP_URL",["ldap://localhost"],"array","--ldap-urls","identifiers",!0],["--ldap-user-main-attribute","DM_LDAP_USER_ATTRIBUTE","uid"],["--ldap-cache-max","DM_LDAP_CACHE_MAX",1e3,"number"],["--ldap-cache-ttl","DM_LDAP_CACHE_TTL",0,"number"],["--ldap-pool-size","DM_LDAP_POOL_SIZE",5,"number"],["--ldap-connection-ttl","DM_LDAP_CONNECTION_TTL",60,"number"],["--schemas-path","DM_SCHEMAS_PATH",_(a(e(import.meta.url)),"..","..","static","schemas")],["--mail-attribute","DM_MAIL_ATTRIBUTE","mail"],["--quota-attribute","DM_QUOTA_ATTRIBUTE","mailQuotaSize"],["--delegation-attribute","DM_DELEGATION_ATTRIBUTE","twakeDelegatedUsers"],["--alias-attribute","DM_ALIAS_ATTRIBUTE","mailAlternateAddress"],["--forward-attribute","DM_FORWARD_ATTRIBUTE","mailForwardingAddress"],["--display-name-attribute","DM_DISPLAY_NAME_ATTRIBUTE","displayName"],["--drive-quota-attribute","DM_DRIVE_QUOTA_ATTRIBUTE","twakeDriveQuota"],["--user-class","DM_USER_CLASSES",["top","twakeAccount","twakeWhitePages"],"array","--user-classes","identifiers"],["--ldap-top-organization","DM_LDAP_TOP_ORGANIZATION",""],["--ldap-organization-class","DM_LDAP_ORGANIZATION_CLASSES",["top","organizationalUnit","twakeDepartment"],"array","--ldap-organization-classes","identifiers"],["--ldap-organization-link-attribute","DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE","twakeDepartmentLink"],["--ldap-organization-path-attribute","DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE","twakeDepartmentPath"],["--ldap-organization-path-separator","DM_LDAP_ORGANIZATION_PATH_SEPARATOR"," / "],["--ldap-organization-max-subnodes","DM_LDAP_ORGANIZATION_MAX_SUBNODES",50,"number"],["--organization-schema","DM_ORGANIZATION_SCHEMA",""],["--enterprise-domain-name-attribute","DM_ENTERPRISE_DOMAIN_NAME_ATTRIBUTE","associatedDomain"],["--enterprise-domain-link-attribute","DM_ENTERPRISE_DOMAIN_LINK_ATTRIBUTE",""],["--storage-backend","DM_STORAGE_BACKEND",""],["--storage-sweep-interval","DM_STORAGE_SWEEP_INTERVAL",600,"number"],["--storage-ldap-base","DM_STORAGE_LDAP_BASE",""],["--storage-ldap-object-class","DM_STORAGE_LDAP_OBJECT_CLASS","applicationProcess"],["--storage-file-directory","DM_STORAGE_FILE_DIRECTORY",""],["--storage-postgres-url","DM_STORAGE_POSTGRES_URL",""],["--storage-postgres-table","DM_STORAGE_POSTGRES_TABLE","ldap_rest_storage"],["--storage-valkey-url","DM_STORAGE_VALKEY_URL",""],["--storage-valkey-prefix","DM_STORAGE_VALKEY_PREFIX","ldap-rest:"],["--bcl-retention","DM_BCL_RETENTION",604800,"number"],["--ldap-group-base","DM_LDAP_GROUP_BASE",""],["--ldap-groups-main-attribute","DM_LDAP_GROUPS_MAIN_ATTRIBUTE","cn"],["--group-class","DM_GROUP_CLASSES",["top","groupOfNames"],"array","--group-classes","identifiers"],["--group-allow-unexistent-members","DM_ALLOW_UNEXISTENT_MEMBERS",!1,"boolean"],["--group-default-attributes","DM_GROUP_DEFAULT_ATTRIBUTES",{},"json"],["--group-dummy-user","DM_GROUP_DUMMY_USER","cn=fakeuser"],["--group-schema","DM_GROUP_SCHEMA",_(a(e(import.meta.url)),"..","..","static","schemas","twake","groups.json")],["--external-members-branch","DM_EXTERNAL_MEMBERS_BRANCH","ou=contacts,dc=example,dc=com"],["--external-branch-class","DM_EXTERNAL_BRANCH_CLASSES",["top","inetOrgPerson"],"array","--external-branch-classes","identifiers"],["--static-path","DM_STATIC_PATH",_(a(e(import.meta.url)),"..","..","static")],["--static-name","DM_STATIC_NAME","static"],["--ldap-flat-schema","DM_LDAP_FLAT_SCHEMA",[],"array","--ldap-flat-schemas"],["--ldap-flat-auto-repair","DM_LDAP_FLAT_AUTO_REPAIR",!0,"boolean"],["--ldap-raw-base","DM_LDAP_RAW_BASE",[],"array","--ldap-raw-bases","dns"],["--ldap-raw-hidden-attribute","DM_LDAP_RAW_HIDDEN_ATTRIBUTES",[],"array","--ldap-raw-hidden-attributes","identifiers"],["--ldap-raw-show-secrets","DM_LDAP_RAW_SHOW_SECRETS",!1,"boolean"],["--ldap-raw-max-results","DM_LDAP_RAW_MAX_RESULTS",200,"number"],["--ldap-raw-schema-cache-ttl","DM_LDAP_RAW_SCHEMA_CACHE_TTL",3600,"number"],["--bulk-import-schemas","DM_BULK_IMPORT_SCHEMAS",""],["--bulk-import-max-file-size","DM_BULK_IMPORT_MAX_FILE_SIZE","10485760"],["--bulk-import-batch-size","DM_BULK_IMPORT_BATCH_SIZE","100"],["--james-webadmin-url","DM_JAMES_WEBADMIN_URL","http://localhost:8000"],["--james-webadmin-token","DM_JAMES_WEBADMIN_TOKEN",""],["--james-signature-template","DM_JAMES_SIGNATURE_TEMPLATE",""],["--ldap-concurrency","DM_LDAP_CONCURRENCY",10,"number"],["--james-concurrency","DM_JAMES_CONCURRENCY",10,"number"],["--james-init-delay","DM_JAMES_INIT_DELAY",1e3,"number"],["--james-retry-attempts","DM_JAMES_RETRY_ATTEMPTS",5,"number"],["--james-retry-delay","DM_JAMES_RETRY_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches","dns"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--james-sync-forwards","DM_JAMES_SYNC_FORWARDS",!1,"boolean"],["--james-team-mailbox-any-domain","DM_JAMES_TEAM_MAILBOX_ANY_DOMAIN",!1,"boolean"],["--twake-drive-webadmin-url","DM_TWAKE_DRIVE_WEBADMIN_URL",""],["--twake-drive-webadmin-token","DM_TWAKE_DRIVE_WEBADMIN_TOKEN",""],["--twake-drive-concurrency","DM_TWAKE_DRIVE_CONCURRENCY",10,"number"],["--twake-drive-domain-attribute","DM_TWAKE_DRIVE_DOMAIN_ATTRIBUTE","twakeCozyDomain"],["--twake-drive-default-domain-template","DM_TWAKE_DRIVE_DEFAULT_DOMAIN_TEMPLATE",""],["--calendar-webadmin-url","DM_CALENDAR_WEBADMIN_URL","http://localhost:8080"],["--calendar-webadmin-token","DM_CALENDAR_WEBADMIN_TOKEN",""],["--calendar-concurrency","DM_CALENDAR_CONCURRENCY",10,"number"],["--calendar-resource-base","DM_CALENDAR_RESOURCE_BASE",""],["--calendar-resource-objectclass","DM_CALENDAR_RESOURCE_OBJECTCLASS",""],["--calendar-resource-creator","DM_CALENDAR_RESOURCE_CREATOR",""],["--calendar-resource-domain","DM_CALENDAR_RESOURCE_DOMAIN",""],["--calendar-firstname-attribute","DM_CALENDAR_FIRSTNAME_ATTRIBUTE","givenName"],["--calendar-lastname-attribute","DM_CALENDAR_LASTNAME_ATTRIBUTE","sn"],["--cozy-admin-url","DM_COZY_ADMIN_URL",""],["--cozy-admin-user","DM_COZY_ADMIN_USER","admin"],["--cozy-admin-passphrase","DM_COZY_ADMIN_PASSPHRASE",""],["--cozy-org-id","DM_COZY_ORG_ID",""],["--cozy-org-domain","DM_COZY_ORG_DOMAIN",""],["--cozy-default-locale","DM_COZY_DEFAULT_LOCALE","fr"],["--cozy-context-name","DM_COZY_CONTEXT_NAME","default"],["--cozy-apps","DM_COZY_APPS","home,drive,settings,notes,dataproxy"],["--cozy-auth-exchange","DM_COZY_AUTH_EXCHANGE","auth"],["--cozy-b2b-exchange","DM_COZY_B2B_EXCHANGE","b2b"],["--cozy-user-created-routing-key","DM_COZY_USER_CREATED_ROUTING_KEY","user.created"],["--cozy-user-deleted-routing-key","DM_COZY_USER_DELETED_ROUTING_KEY","domain.user.deleted"],["--cloudery-manager-url","DM_CLOUDERY_MANAGER_URL",""],["--cloudery-manager-token","DM_CLOUDERY_MANAGER_TOKEN",""],["--cloudery-offer","DM_CLOUDERY_OFFER","b2b_twake_default"],["--cloudery-domain","DM_CLOUDERY_DOMAIN",""],["--cloudery-user-branch","DM_CLOUDERY_USER_BRANCH",""],["--cloudery-org-id-header","DM_CLOUDERY_ORG_ID_HEADER","x-cloudery-org-id"],["--cloudery-org-role-header","DM_CLOUDERY_ORG_ROLE_HEADER","x-cloudery-org-role"],["--cloudery-default-org-role","DM_CLOUDERY_DEFAULT_ORG_ROLE","member"],["--cloudery-fqdn-attribute","DM_CLOUDERY_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--cloudery-org-id-attribute","DM_CLOUDERY_ORG_ID_ATTRIBUTE","twakeOrganizationId"],["--cloudery-org-role-attribute","DM_CLOUDERY_ORG_ROLE_ATTRIBUTE","twakeOrganizationRole"],["--cloudery-phones-attribute","DM_CLOUDERY_PHONES_ATTRIBUTE","twakePhones"],["--cloudery-invited-attribute","DM_CLOUDERY_INVITED_ATTRIBUTE","twakeInvited"],["--cloudery-default-locale","DM_CLOUDERY_DEFAULT_LOCALE","en"],["--cloudery-workflow-poll-interval-ms","DM_CLOUDERY_WORKFLOW_POLL_INTERVAL_MS",2e3,"number"],["--cloudery-workflow-max-attempts","DM_CLOUDERY_WORKFLOW_MAX_ATTEMPTS",60,"number"],["--twake-lifecycle-role-attribute","DM_TWAKE_LIFECYCLE_ROLE_ATTRIBUTE",""],["--twake-lifecycle-lock-attribute","DM_TWAKE_LIFECYCLE_LOCK_ATTRIBUTE",""],["--twake-lifecycle-lock-value","DM_TWAKE_LIFECYCLE_LOCK_VALUE",""],["--twake-lifecycle-deleted-attribute","DM_TWAKE_LIFECYCLE_DELETED_ATTRIBUTE",""],["--twake-lifecycle-deleted-value","DM_TWAKE_LIFECYCLE_DELETED_VALUE","TRUE"],["--twake-lifecycle-deleted-at-attribute","DM_TWAKE_LIFECYCLE_DELETED_AT_ATTRIBUTE",""],["--twake-lifecycle-deleted-at-format","DM_TWAKE_LIFECYCLE_DELETED_AT_FORMAT","iso8601"],["--twake-lifecycle-reason-attribute","DM_TWAKE_LIFECYCLE_REASON_ATTRIBUTE",""],["--twake-lifecycle-rules","DM_TWAKE_LIFECYCLE_RULES",""],["--twake-lifecycle-member-attribute","DM_TWAKE_LIFECYCLE_MEMBER_ATTRIBUTE","member"],["--twake-group-base","DM_TWAKE_GROUP_BASE",""],["--twake-group-user-base","DM_TWAKE_GROUP_USER_BASE",""],["--twake-group-organization-dn","DM_TWAKE_GROUP_ORGANIZATION_DN",""],["--twake-group-organization-status-attribute","DM_TWAKE_GROUP_ORGANIZATION_STATUS_ATTRIBUTE","twakeOrgStatus"],["--twake-group-organization-deleted-value","DM_TWAKE_GROUP_ORGANIZATION_DELETED_VALUE","deleted"],["--twake-group-display-name-attribute","DM_TWAKE_GROUP_DISPLAY_NAME_ATTRIBUTE","twakeDisplayName"],["--twake-group-color-attribute","DM_TWAKE_GROUP_COLOR_ATTRIBUTE","twakeGroupColor"],["--twake-group-created-at-attribute","DM_TWAKE_GROUP_CREATED_AT_ATTRIBUTE","twakeCreatedAt"],["--twake-group-max-page-limit","DM_TWAKE_GROUP_MAX_PAGE_LIMIT",1e3,"number"],["--twake-group-member-fields","DM_TWAKE_GROUP_MEMBER_FIELDS",{},"json"],["--twake-space-base","DM_TWAKE_SPACE_BASE",""],["--twake-space-class","DM_TWAKE_SPACE_CLASS",["top","twakeSpace"],"array",null,"identifiers"],["--twake-space-display-name-attribute","DM_TWAKE_SPACE_DISPLAY_NAME_ATTRIBUTE","twakeDisplayName"],["--twake-space-admin-attribute","DM_TWAKE_SPACE_ADMIN_ATTRIBUTE","twakeSpaceAdmin"],["--twake-space-editor-attribute","DM_TWAKE_SPACE_EDITOR_ATTRIBUTE","twakeSpaceEditor"],["--twake-space-viewer-attribute","DM_TWAKE_SPACE_VIEWER_ATTRIBUTE","twakeSpaceViewer"],["--twake-space-user-role-attribute","DM_TWAKE_SPACE_USER_ROLE_ATTRIBUTE",""],["--twake-space-exchange","DM_TWAKE_SPACE_EXCHANGE","space"],["--twake-space-sync-queue","DM_TWAKE_SPACE_SYNC_QUEUE","twake.space.sync.requested.ldap-rest"],["--twake-tombstone-dn","DM_TWAKE_TOMBSTONE_DN",[],"array",null,"dns"],["--twake-tombstone-default-reason","DM_TWAKE_TOMBSTONE_DEFAULT_REASON","deleted"],["--twake-tombstone-reason-header","DM_TWAKE_TOMBSTONE_REASON_HEADER","x-deletion-reason"],["--twake-tombstone-reasons","DM_TWAKE_TOMBSTONE_REASONS",[],"array",null,"identifiers"],["--twake-tombstone-clear-attributes","DM_TWAKE_TOMBSTONE_CLEAR_ATTRIBUTES",[],"array",null,"identifiers"],["--twake-tombstone-erase-min-age","DM_TWAKE_TOMBSTONE_ERASE_MIN_AGE",2592e3,"number"],["--twake-tombstone-group-bases","DM_TWAKE_TOMBSTONE_GROUP_BASES",[],"array",null,"dns"],["--twake-instance-dn","DM_TWAKE_INSTANCE_DN",[],"array",null,"dns"],["--twake-instance-skip-attribute","DM_TWAKE_INSTANCE_SKIP_ATTRIBUTE",""],["--twake-instance-skip-value","DM_TWAKE_INSTANCE_SKIP_VALUE",""],["--twake-instance-provider","DM_TWAKE_INSTANCE_PROVIDER","cloudery"],["--twake-instance-id","DM_TWAKE_INSTANCE_ID","{uid}"],["--twake-instance-fqdn-attribute","DM_TWAKE_INSTANCE_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--twake-instance-sent-attribute","DM_TWAKE_INSTANCE_SENT_ATTRIBUTE","twakeCreatedEventAt"],["--twake-instance-locale","DM_TWAKE_INSTANCE_LOCALE",""],["--twake-instance-timeout","DM_TWAKE_INSTANCE_TIMEOUT",3e4,"number"],["--twake-instance-cloudery-url","DM_TWAKE_INSTANCE_CLOUDERY_URL",""],["--twake-instance-cloudery-token","DM_TWAKE_INSTANCE_CLOUDERY_TOKEN",""],["--twake-instance-cloudery-offer","DM_TWAKE_INSTANCE_CLOUDERY_OFFER","b2b_twake_default"],["--twake-instance-cloudery-domain","DM_TWAKE_INSTANCE_CLOUDERY_DOMAIN",""],["--twake-instance-cloudery-organization-offer","DM_TWAKE_INSTANCE_CLOUDERY_ORGANIZATION_OFFER",""],["--twake-instance-cozy-url","DM_TWAKE_INSTANCE_COZY_URL",""],["--twake-instance-cozy-user","DM_TWAKE_INSTANCE_COZY_USER","admin"],["--twake-instance-cozy-passphrase","DM_TWAKE_INSTANCE_COZY_PASSPHRASE",""],["--twake-instance-cozy-domain","DM_TWAKE_INSTANCE_COZY_DOMAIN",""],["--twake-instance-cozy-context","DM_TWAKE_INSTANCE_COZY_CONTEXT","default"],["--twake-instance-cozy-apps","DM_TWAKE_INSTANCE_COZY_APPS","home,drive,settings,notes,dataproxy"],["--twake-instance-cozy-org-id","DM_TWAKE_INSTANCE_COZY_ORG_ID",""],["--twake-instance-cozy-org-domain","DM_TWAKE_INSTANCE_COZY_ORG_DOMAIN",""],["--twake-instance-auth-exchange","DM_TWAKE_INSTANCE_AUTH_EXCHANGE","auth"],["--twake-instance-b2b-exchange","DM_TWAKE_INSTANCE_B2B_EXCHANGE","b2b"],["--twake-instance-user-created-key","DM_TWAKE_INSTANCE_USER_CREATED_KEY","user.created"],["--twake-instance-organization-created-key","DM_TWAKE_INSTANCE_ORGANIZATION_CREATED_KEY","organization.created"],["--twake-instance-workplace-created-key","DM_TWAKE_INSTANCE_WORKPLACE_CREATED_KEY","workplace.created"],["--twake-instance-queue","DM_TWAKE_INSTANCE_QUEUE","workplace.created.ldap-rest"],["--twake-instance-organization-base","DM_TWAKE_INSTANCE_ORGANIZATION_BASE",""],["--twake-instance-organization-id-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_ID_ATTRIBUTE","ou"],["--twake-instance-organization-domain-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_DOMAIN_ATTRIBUTE","twakeDomain"],["--twake-instance-organization-name-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_NAME_ATTRIBUTE","description"],["--twake-instance-organization-fqdn-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_FQDN_ATTRIBUTE",""],["--rabbitmq-url","DM_RABBITMQ_URL",""],["--applicative-account-base","DM_APPLICATIVE_ACCOUNT_BASE",""],["--max-app-accounts","DM_MAX_APP_ACCOUNTS",5,"number"],["--app-accounts-user-attribute","DM_APP_ACCOUNTS_USER_ATTRIBUTE",""],["--applicative-account-attribute","DM_APPLICATIVE_ACCOUNT_ATTRIBUTES",["objectClass","cn","sn","givenName","displayName","description"],"array","--applicative-account-attributes","identifiers"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes","identifiers"],["--trash-base","DM_TRASH_BASE",""],["--trash-watched-bases","DM_TRASH_WATCHED_BASES",""],["--trash-add-metadata","DM_TRASH_ADD_METADATA","true"],["--trash-auto-create","DM_TRASH_AUTO_CREATE","true"],["--llng-ini","DM_LLNG_INI","/etc/lemonldap-ng/lemonldap-ng.ini"],["--llng-username-header","DM_LLNG_USERNAME_HEADER",""],["--auth-path-prefix","DM_AUTH_PATH_PREFIX",[],"array","--auth-path-prefixes"],["--auth-token","DM_AUTH_TOKENS",[],"array","--auth-tokens"],["--auth-fake-user","DM_AUTH_FAKE_USER",""],["--auth-totp","DM_AUTH_TOTP",[],"array","--auth-totps","phrases"],["--auth-totp-window","DM_AUTH_TOTP_WINDOW",1,"number"],["--auth-totp-step","DM_AUTH_TOTP_STEP",30,"number"],["--auth-hmac","DM_AUTH_HMAC",[],"array","--auth-hmacs","phrases"],["--auth-hmac-window","DM_AUTH_HMAC_WINDOW",12e4,"number"],["--authz-per-route","DM_AUTHZ_PER_ROUTES",[],"array","--authz-per-routes"],["--authz-unresolved-user","DM_AUTHZ_UNRESOLVED_USER","deny"],["--authz-identity","DM_AUTHZ_IDENTITY","req.user"],["--authz-for","DM_AUTHZ_FOR",[],"array",null,"identifiers"],["--authz-combine","DM_AUTHZ_COMBINE",!1,"boolean"],["--authz-scope-source","DM_AUTHZ_SCOPE_SOURCE",""],["--authz-filter-attached-entries","DM_AUTHZ_FILTER_ATTACHED_ENTRIES",!1,"boolean"],["--authz-transit-branch","DM_AUTHZ_TRANSIT_BRANCH",""],["--authz-transit-origin-attribute","DM_AUTHZ_TRANSIT_ORIGIN_ATTRIBUTE",""],["--authz-per-branch-config","DM_AUTHZ_PER_BRANCH_CONFIG",{default:{read:!0,write:!1,delete:!1}},"json"],["--authz-per-branch-cache-ttl","DM_AUTHZ_PER_BRANCH_CACHE_TTL",60,"number"],["--authz-dynamic-bypass","DM_AUTHZ_DYNAMIC_BYPASS",[],"array",null,"identifiers"],["--authz-dynamic-base","DM_AUTHZ_DYNAMIC_BASE",""],["--authz-dynamic-cache-ttl","DM_AUTHZ_DYNAMIC_CACHE_TTL",60,"number"],["--authz-dynamic-token-attribute","DM_AUTHZ_DYNAMIC_TOKEN_ATTRIBUTE","userPassword"],["--authz-dynamic-config-attribute","DM_AUTHZ_DYNAMIC_CONFIG_ATTRIBUTE","description"],["--authz-dynamic-tenant-attribute","DM_AUTHZ_DYNAMIC_TENANT_ATTRIBUTE","cn"],["--authz-dynamic-reload-endpoint","DM_AUTHZ_DYNAMIC_RELOAD_ENDPOINT",!1,"boolean"],["--authz-local-admin-attribute","DM_AUTHZ_LOCAL_ADMIN_ATTRIBUTE","twakeLocalAdminLink"],["--authz-global-branch","DM_AUTHZ_GLOBAL_BRANCHES",[],"array","--authz-global-branches","dns"],["--authz-global-write-branch","DM_AUTHZ_GLOBAL_WRITE_BRANCHES",[],"array","--authz-global-write-branches","dns"],["--authz-global-organization","DM_AUTHZ_GLOBAL_ORGANIZATION",""],["--oidc-server","DM_OIDC_SERVER",""],["--oidc-username-claim","DM_OIDC_USERNAME_CLAIM","sub"],["--oidc-client-id","DM_OIDC_CLIENT_ID",""],["--oidc-client-secret","DM_OIDC_CLIENT_SECRET",""],["--base-url","DM_BASE_URL",""],["--rate-limit-window-ms","DM_RATE_LIMIT_WINDOW_MS",9e5,"number"],["--rate-limit-max","DM_RATE_LIMIT_MAX",100,"number"],["--crowdsec-url","DM_CROWDSEC_URL","http://localhost:8080/v1/decisions"],["--crowdsec-api-key","DM_CROWDSEC_API_KEY",""],["--crowdsec-cache-ttl","DM_CROWDSEC_CACHE_TTL",60,"number"],["--trusted-proxy","DM_TRUSTED_PROXIES",[],"array","--trusted-proxies","identifiers"],["--trusted-proxy-auth-header","DM_TRUSTED_PROXY_AUTH_HEADER","Auth-User"],["--ppolicy-default-dn","DM_PPOLICY_DEFAULT_DN",""],["--ppolicy-base","DM_PPOLICY_BASE",""],["--ppolicy-warn-days","DM_PPOLICY_WARN_DAYS",14,"number"],["--ppolicy-validate-complexity","DM_PPOLICY_VALIDATE_COMPLEXITY",!1,"boolean"],["--ppolicy-min-length","DM_PPOLICY_MIN_LENGTH",12,"number"],["--ppolicy-require-uppercase","DM_PPOLICY_REQUIRE_UPPERCASE",!0,"boolean"],["--ppolicy-require-lowercase","DM_PPOLICY_REQUIRE_LOWERCASE",!0,"boolean"],["--ppolicy-require-digit","DM_PPOLICY_REQUIRE_DIGIT",!0,"boolean"],["--ppolicy-require-special","DM_PPOLICY_REQUIRE_SPECIAL",!0,"boolean"],["--ldap-users-base","DM_LDAP_USERS_BASE",""],["--scim-prefix","DM_SCIM_PREFIX","/scim/v2"],["--scim-user-base","DM_SCIM_USER_BASE",""],["--scim-group-base","DM_SCIM_GROUP_BASE",""],["--scim-user-base-template","DM_SCIM_USER_BASE_TEMPLATE",""],["--scim-group-base-template","DM_SCIM_GROUP_BASE_TEMPLATE",""],["--scim-base-map","DM_SCIM_BASE_MAP",""],["--scim-user-base-header","DM_SCIM_USER_BASE_HEADER",""],["--scim-group-base-header","DM_SCIM_GROUP_BASE_HEADER",""],["--scim-base-header-root","DM_SCIM_BASE_HEADER_ROOT",""],["--scim-user-object-class","DM_SCIM_USER_OBJECT_CLASSES",["top","inetOrgPerson","organizationalPerson","person"],"array","--scim-user-object-classes","identifiers"],["--scim-user-rdn-attribute","DM_SCIM_USER_RDN_ATTRIBUTE","uid"],["--scim-user-lock-attribute","DM_SCIM_USER_LOCK_ATTRIBUTE","pwdAccountLockedTime"],["--scim-user-lock-value","DM_SCIM_USER_LOCK_VALUE",""],["--scim-group-object-class","DM_SCIM_GROUP_OBJECT_CLASSES",["top","groupOfNames"],"array","--scim-group-object-classes","identifiers"],["--scim-group-rdn-attribute","DM_SCIM_GROUP_RDN_ATTRIBUTE","cn"],["--scim-group-external-id-attribute","DM_SCIM_GROUP_EXTERNAL_ID_ATTRIBUTE",""],["--scim-id-attribute","DM_SCIM_ID_ATTRIBUTE","rdn"],["--scim-user-mapping","DM_SCIM_USER_MAPPING",""],["--scim-group-mapping","DM_SCIM_GROUP_MAPPING",""],["--scim-max-results","DM_SCIM_MAX_RESULTS",200,"number"],["--scim-max-scanned","DM_SCIM_MAX_SCANNED",1e4,"number"],["--scim-bulk-max-operations","DM_SCIM_BULK_MAX_OPERATIONS",100,"number"],["--scim-bulk-max-payload-size","DM_SCIM_BULK_MAX_PAYLOAD_SIZE",1048576,"number"],["--scim-etag","DM_SCIM_ETAG",!1,"boolean"],["--scim-base-url","DM_SCIM_BASE_URL",""]];export{t as default};
1
+ import{fileURLToPath as e}from"url";import{join as _,dirname as a}from"path";const t="frame-ancestors 'self'",E="SAMEORIGIN",r=[["--port","DM_PORT",8081,"number"],["--plugin","DM_PLUGINS",[],"array","--plugins","identifiers"],["--log-level","DM_LOG_LEVEL","notice"],["--logger","DM_LOGGER","console"],["--api-prefix","DM_API_PREFIX","/api"],["--mail-domain","DM_MAIL_DOMAIN",[],"array","--mail-domains","identifiers"],["--ldap-base","DM_LDAP_BASE",""],["--ldap-dn","DM_LDAP_DN","cn=admin,dc=example,dc=com"],["--ldap-pwd","DM_LDAP_PWD","admin"],["--ldap-url","DM_LDAP_URL",["ldap://localhost"],"array","--ldap-urls","identifiers",!0],["--ldap-user-main-attribute","DM_LDAP_USER_ATTRIBUTE","uid"],["--ldap-cache-max","DM_LDAP_CACHE_MAX",1e3,"number"],["--ldap-cache-ttl","DM_LDAP_CACHE_TTL",0,"number"],["--ldap-pool-size","DM_LDAP_POOL_SIZE",5,"number"],["--ldap-connection-ttl","DM_LDAP_CONNECTION_TTL",60,"number"],["--schemas-path","DM_SCHEMAS_PATH",_(a(e(import.meta.url)),"..","..","static","schemas")],["--mail-attribute","DM_MAIL_ATTRIBUTE","mail"],["--quota-attribute","DM_QUOTA_ATTRIBUTE","mailQuotaSize"],["--delegation-attribute","DM_DELEGATION_ATTRIBUTE","twakeDelegatedUsers"],["--alias-attribute","DM_ALIAS_ATTRIBUTE","mailAlternateAddress"],["--forward-attribute","DM_FORWARD_ATTRIBUTE","mailForwardingAddress"],["--display-name-attribute","DM_DISPLAY_NAME_ATTRIBUTE","displayName"],["--drive-quota-attribute","DM_DRIVE_QUOTA_ATTRIBUTE","twakeDriveQuota"],["--user-class","DM_USER_CLASSES",["top","twakeAccount","twakeWhitePages"],"array","--user-classes","identifiers"],["--ldap-top-organization","DM_LDAP_TOP_ORGANIZATION",""],["--ldap-organization-class","DM_LDAP_ORGANIZATION_CLASSES",["top","organizationalUnit","twakeDepartment"],"array","--ldap-organization-classes","identifiers"],["--ldap-organization-link-attribute","DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE","twakeDepartmentLink"],["--ldap-organization-path-attribute","DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE","twakeDepartmentPath"],["--ldap-organization-path-separator","DM_LDAP_ORGANIZATION_PATH_SEPARATOR"," / "],["--ldap-organization-max-subnodes","DM_LDAP_ORGANIZATION_MAX_SUBNODES",50,"number"],["--organization-schema","DM_ORGANIZATION_SCHEMA",""],["--enterprise-domain-name-attribute","DM_ENTERPRISE_DOMAIN_NAME_ATTRIBUTE","associatedDomain"],["--enterprise-domain-link-attribute","DM_ENTERPRISE_DOMAIN_LINK_ATTRIBUTE",""],["--storage-backend","DM_STORAGE_BACKEND",""],["--storage-sweep-interval","DM_STORAGE_SWEEP_INTERVAL",600,"number"],["--storage-ldap-base","DM_STORAGE_LDAP_BASE",""],["--storage-ldap-object-class","DM_STORAGE_LDAP_OBJECT_CLASS","applicationProcess"],["--storage-file-directory","DM_STORAGE_FILE_DIRECTORY",""],["--storage-postgres-url","DM_STORAGE_POSTGRES_URL",""],["--storage-postgres-table","DM_STORAGE_POSTGRES_TABLE","ldap_rest_storage"],["--storage-valkey-url","DM_STORAGE_VALKEY_URL",""],["--storage-valkey-prefix","DM_STORAGE_VALKEY_PREFIX","ldap-rest:"],["--bcl-retention","DM_BCL_RETENTION",604800,"number"],["--ldap-group-base","DM_LDAP_GROUP_BASE",""],["--ldap-groups-main-attribute","DM_LDAP_GROUPS_MAIN_ATTRIBUTE","cn"],["--group-class","DM_GROUP_CLASSES",["top","groupOfNames"],"array","--group-classes","identifiers"],["--group-allow-unexistent-members","DM_ALLOW_UNEXISTENT_MEMBERS",!1,"boolean"],["--group-default-attributes","DM_GROUP_DEFAULT_ATTRIBUTES",{},"json"],["--group-dummy-user","DM_GROUP_DUMMY_USER","cn=fakeuser"],["--group-schema","DM_GROUP_SCHEMA",_(a(e(import.meta.url)),"..","..","static","schemas","twake","groups.json")],["--external-members-branch","DM_EXTERNAL_MEMBERS_BRANCH","ou=contacts,dc=example,dc=com"],["--external-branch-class","DM_EXTERNAL_BRANCH_CLASSES",["top","inetOrgPerson"],"array","--external-branch-classes","identifiers"],["--static-path","DM_STATIC_PATH",_(a(e(import.meta.url)),"..","..","static")],["--static-name","DM_STATIC_NAME","static"],["--static-csp","DM_STATIC_CSP",t],["--static-frame-options","DM_STATIC_FRAME_OPTIONS",E],["--ldap-flat-schema","DM_LDAP_FLAT_SCHEMA",[],"array","--ldap-flat-schemas"],["--ldap-flat-auto-repair","DM_LDAP_FLAT_AUTO_REPAIR",!0,"boolean"],["--ldap-raw-base","DM_LDAP_RAW_BASE",[],"array","--ldap-raw-bases","dns"],["--ldap-raw-hidden-attribute","DM_LDAP_RAW_HIDDEN_ATTRIBUTES",[],"array","--ldap-raw-hidden-attributes","identifiers"],["--ldap-raw-show-secrets","DM_LDAP_RAW_SHOW_SECRETS",!1,"boolean"],["--ldap-raw-max-results","DM_LDAP_RAW_MAX_RESULTS",200,"number"],["--ldap-raw-schema-cache-ttl","DM_LDAP_RAW_SCHEMA_CACHE_TTL",3600,"number"],["--bulk-import-schemas","DM_BULK_IMPORT_SCHEMAS",""],["--bulk-import-max-file-size","DM_BULK_IMPORT_MAX_FILE_SIZE","10485760"],["--bulk-import-batch-size","DM_BULK_IMPORT_BATCH_SIZE","100"],["--james-webadmin-url","DM_JAMES_WEBADMIN_URL","http://localhost:8000"],["--james-webadmin-token","DM_JAMES_WEBADMIN_TOKEN",""],["--james-signature-template","DM_JAMES_SIGNATURE_TEMPLATE",""],["--ldap-concurrency","DM_LDAP_CONCURRENCY",10,"number"],["--james-concurrency","DM_JAMES_CONCURRENCY",10,"number"],["--james-init-delay","DM_JAMES_INIT_DELAY",1e3,"number"],["--james-retry-attempts","DM_JAMES_RETRY_ATTEMPTS",5,"number"],["--james-retry-delay","DM_JAMES_RETRY_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches","dns"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--james-sync-forwards","DM_JAMES_SYNC_FORWARDS",!1,"boolean"],["--james-team-mailbox-any-domain","DM_JAMES_TEAM_MAILBOX_ANY_DOMAIN",!1,"boolean"],["--twake-drive-webadmin-url","DM_TWAKE_DRIVE_WEBADMIN_URL",""],["--twake-drive-webadmin-token","DM_TWAKE_DRIVE_WEBADMIN_TOKEN",""],["--twake-drive-concurrency","DM_TWAKE_DRIVE_CONCURRENCY",10,"number"],["--twake-drive-domain-attribute","DM_TWAKE_DRIVE_DOMAIN_ATTRIBUTE","twakeCozyDomain"],["--twake-drive-default-domain-template","DM_TWAKE_DRIVE_DEFAULT_DOMAIN_TEMPLATE",""],["--calendar-webadmin-url","DM_CALENDAR_WEBADMIN_URL","http://localhost:8080"],["--calendar-webadmin-token","DM_CALENDAR_WEBADMIN_TOKEN",""],["--calendar-concurrency","DM_CALENDAR_CONCURRENCY",10,"number"],["--calendar-resource-base","DM_CALENDAR_RESOURCE_BASE",""],["--calendar-resource-objectclass","DM_CALENDAR_RESOURCE_OBJECTCLASS",""],["--calendar-resource-creator","DM_CALENDAR_RESOURCE_CREATOR",""],["--calendar-resource-domain","DM_CALENDAR_RESOURCE_DOMAIN",""],["--calendar-firstname-attribute","DM_CALENDAR_FIRSTNAME_ATTRIBUTE","givenName"],["--calendar-lastname-attribute","DM_CALENDAR_LASTNAME_ATTRIBUTE","sn"],["--cozy-admin-url","DM_COZY_ADMIN_URL",""],["--cozy-admin-user","DM_COZY_ADMIN_USER","admin"],["--cozy-admin-passphrase","DM_COZY_ADMIN_PASSPHRASE",""],["--cozy-org-id","DM_COZY_ORG_ID",""],["--cozy-org-domain","DM_COZY_ORG_DOMAIN",""],["--cozy-default-locale","DM_COZY_DEFAULT_LOCALE","fr"],["--cozy-context-name","DM_COZY_CONTEXT_NAME","default"],["--cozy-apps","DM_COZY_APPS","home,drive,settings,notes,dataproxy"],["--cozy-auth-exchange","DM_COZY_AUTH_EXCHANGE","auth"],["--cozy-b2b-exchange","DM_COZY_B2B_EXCHANGE","b2b"],["--cozy-user-created-routing-key","DM_COZY_USER_CREATED_ROUTING_KEY","user.created"],["--cozy-user-deleted-routing-key","DM_COZY_USER_DELETED_ROUTING_KEY","domain.user.deleted"],["--cloudery-manager-url","DM_CLOUDERY_MANAGER_URL",""],["--cloudery-manager-token","DM_CLOUDERY_MANAGER_TOKEN",""],["--cloudery-offer","DM_CLOUDERY_OFFER","b2b_twake_default"],["--cloudery-domain","DM_CLOUDERY_DOMAIN",""],["--cloudery-user-branch","DM_CLOUDERY_USER_BRANCH",""],["--cloudery-org-id-header","DM_CLOUDERY_ORG_ID_HEADER","x-cloudery-org-id"],["--cloudery-org-role-header","DM_CLOUDERY_ORG_ROLE_HEADER","x-cloudery-org-role"],["--cloudery-default-org-role","DM_CLOUDERY_DEFAULT_ORG_ROLE","member"],["--cloudery-fqdn-attribute","DM_CLOUDERY_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--cloudery-org-id-attribute","DM_CLOUDERY_ORG_ID_ATTRIBUTE","twakeOrganizationId"],["--cloudery-org-role-attribute","DM_CLOUDERY_ORG_ROLE_ATTRIBUTE","twakeOrganizationRole"],["--cloudery-phones-attribute","DM_CLOUDERY_PHONES_ATTRIBUTE","twakePhones"],["--cloudery-invited-attribute","DM_CLOUDERY_INVITED_ATTRIBUTE","twakeInvited"],["--cloudery-default-locale","DM_CLOUDERY_DEFAULT_LOCALE","en"],["--cloudery-workflow-poll-interval-ms","DM_CLOUDERY_WORKFLOW_POLL_INTERVAL_MS",2e3,"number"],["--cloudery-workflow-max-attempts","DM_CLOUDERY_WORKFLOW_MAX_ATTEMPTS",60,"number"],["--twake-lifecycle-role-attribute","DM_TWAKE_LIFECYCLE_ROLE_ATTRIBUTE",""],["--twake-lifecycle-lock-attribute","DM_TWAKE_LIFECYCLE_LOCK_ATTRIBUTE",""],["--twake-lifecycle-lock-value","DM_TWAKE_LIFECYCLE_LOCK_VALUE",""],["--twake-lifecycle-deleted-attribute","DM_TWAKE_LIFECYCLE_DELETED_ATTRIBUTE",""],["--twake-lifecycle-deleted-value","DM_TWAKE_LIFECYCLE_DELETED_VALUE","TRUE"],["--twake-lifecycle-deleted-at-attribute","DM_TWAKE_LIFECYCLE_DELETED_AT_ATTRIBUTE",""],["--twake-lifecycle-deleted-at-format","DM_TWAKE_LIFECYCLE_DELETED_AT_FORMAT","iso8601"],["--twake-lifecycle-reason-attribute","DM_TWAKE_LIFECYCLE_REASON_ATTRIBUTE",""],["--twake-lifecycle-rules","DM_TWAKE_LIFECYCLE_RULES",""],["--twake-lifecycle-member-attribute","DM_TWAKE_LIFECYCLE_MEMBER_ATTRIBUTE","member"],["--twake-group-base","DM_TWAKE_GROUP_BASE",""],["--twake-group-user-base","DM_TWAKE_GROUP_USER_BASE",""],["--twake-group-organization-dn","DM_TWAKE_GROUP_ORGANIZATION_DN",""],["--twake-group-organization-status-attribute","DM_TWAKE_GROUP_ORGANIZATION_STATUS_ATTRIBUTE","twakeOrgStatus"],["--twake-group-organization-deleted-value","DM_TWAKE_GROUP_ORGANIZATION_DELETED_VALUE","deleted"],["--twake-group-display-name-attribute","DM_TWAKE_GROUP_DISPLAY_NAME_ATTRIBUTE","twakeDisplayName"],["--twake-group-color-attribute","DM_TWAKE_GROUP_COLOR_ATTRIBUTE","twakeGroupColor"],["--twake-group-created-at-attribute","DM_TWAKE_GROUP_CREATED_AT_ATTRIBUTE","twakeCreatedAt"],["--twake-group-max-page-limit","DM_TWAKE_GROUP_MAX_PAGE_LIMIT",1e3,"number"],["--twake-group-member-fields","DM_TWAKE_GROUP_MEMBER_FIELDS",{},"json"],["--twake-space-base","DM_TWAKE_SPACE_BASE",""],["--twake-space-class","DM_TWAKE_SPACE_CLASS",["top","twakeSpace"],"array",null,"identifiers"],["--twake-space-display-name-attribute","DM_TWAKE_SPACE_DISPLAY_NAME_ATTRIBUTE","twakeDisplayName"],["--twake-space-admin-attribute","DM_TWAKE_SPACE_ADMIN_ATTRIBUTE","twakeSpaceAdmin"],["--twake-space-editor-attribute","DM_TWAKE_SPACE_EDITOR_ATTRIBUTE","twakeSpaceEditor"],["--twake-space-viewer-attribute","DM_TWAKE_SPACE_VIEWER_ATTRIBUTE","twakeSpaceViewer"],["--twake-space-user-role-attribute","DM_TWAKE_SPACE_USER_ROLE_ATTRIBUTE",""],["--twake-space-exchange","DM_TWAKE_SPACE_EXCHANGE","space"],["--twake-space-sync-queue","DM_TWAKE_SPACE_SYNC_QUEUE","twake.space.sync.requested.ldap-rest"],["--twake-tombstone-dn","DM_TWAKE_TOMBSTONE_DN",[],"array",null,"dns"],["--twake-tombstone-default-reason","DM_TWAKE_TOMBSTONE_DEFAULT_REASON","deleted"],["--twake-tombstone-reason-header","DM_TWAKE_TOMBSTONE_REASON_HEADER","x-deletion-reason"],["--twake-tombstone-reasons","DM_TWAKE_TOMBSTONE_REASONS",[],"array",null,"identifiers"],["--twake-tombstone-clear-attributes","DM_TWAKE_TOMBSTONE_CLEAR_ATTRIBUTES",[],"array",null,"identifiers"],["--twake-tombstone-erase-min-age","DM_TWAKE_TOMBSTONE_ERASE_MIN_AGE",2592e3,"number"],["--twake-tombstone-group-bases","DM_TWAKE_TOMBSTONE_GROUP_BASES",[],"array",null,"dns"],["--twake-instance-dn","DM_TWAKE_INSTANCE_DN",[],"array",null,"dns"],["--twake-instance-skip-attribute","DM_TWAKE_INSTANCE_SKIP_ATTRIBUTE",""],["--twake-instance-skip-value","DM_TWAKE_INSTANCE_SKIP_VALUE",""],["--twake-instance-provider","DM_TWAKE_INSTANCE_PROVIDER","cloudery"],["--twake-instance-id","DM_TWAKE_INSTANCE_ID","{uid}"],["--twake-instance-fqdn-attribute","DM_TWAKE_INSTANCE_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--twake-instance-sent-attribute","DM_TWAKE_INSTANCE_SENT_ATTRIBUTE","twakeCreatedEventAt"],["--twake-instance-locale","DM_TWAKE_INSTANCE_LOCALE",""],["--twake-instance-timeout","DM_TWAKE_INSTANCE_TIMEOUT",3e4,"number"],["--twake-instance-cloudery-url","DM_TWAKE_INSTANCE_CLOUDERY_URL",""],["--twake-instance-cloudery-token","DM_TWAKE_INSTANCE_CLOUDERY_TOKEN",""],["--twake-instance-cloudery-offer","DM_TWAKE_INSTANCE_CLOUDERY_OFFER","b2b_twake_default"],["--twake-instance-cloudery-domain","DM_TWAKE_INSTANCE_CLOUDERY_DOMAIN",""],["--twake-instance-cloudery-organization-offer","DM_TWAKE_INSTANCE_CLOUDERY_ORGANIZATION_OFFER",""],["--twake-instance-cozy-url","DM_TWAKE_INSTANCE_COZY_URL",""],["--twake-instance-cozy-user","DM_TWAKE_INSTANCE_COZY_USER","admin"],["--twake-instance-cozy-passphrase","DM_TWAKE_INSTANCE_COZY_PASSPHRASE",""],["--twake-instance-cozy-domain","DM_TWAKE_INSTANCE_COZY_DOMAIN",""],["--twake-instance-cozy-context","DM_TWAKE_INSTANCE_COZY_CONTEXT","default"],["--twake-instance-cozy-apps","DM_TWAKE_INSTANCE_COZY_APPS","home,drive,settings,notes,dataproxy"],["--twake-instance-cozy-org-id","DM_TWAKE_INSTANCE_COZY_ORG_ID",""],["--twake-instance-cozy-org-domain","DM_TWAKE_INSTANCE_COZY_ORG_DOMAIN",""],["--twake-instance-auth-exchange","DM_TWAKE_INSTANCE_AUTH_EXCHANGE","auth"],["--twake-instance-b2b-exchange","DM_TWAKE_INSTANCE_B2B_EXCHANGE","b2b"],["--twake-instance-user-created-key","DM_TWAKE_INSTANCE_USER_CREATED_KEY","user.created"],["--twake-instance-organization-created-key","DM_TWAKE_INSTANCE_ORGANIZATION_CREATED_KEY","organization.created"],["--twake-instance-workplace-created-key","DM_TWAKE_INSTANCE_WORKPLACE_CREATED_KEY","workplace.created"],["--twake-instance-queue","DM_TWAKE_INSTANCE_QUEUE","workplace.created.ldap-rest"],["--twake-instance-organization-base","DM_TWAKE_INSTANCE_ORGANIZATION_BASE",""],["--twake-instance-organization-id-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_ID_ATTRIBUTE","ou"],["--twake-instance-organization-domain-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_DOMAIN_ATTRIBUTE","twakeDomain"],["--twake-instance-organization-name-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_NAME_ATTRIBUTE","description"],["--twake-instance-organization-fqdn-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_FQDN_ATTRIBUTE",""],["--rabbitmq-url","DM_RABBITMQ_URL",""],["--applicative-account-base","DM_APPLICATIVE_ACCOUNT_BASE",""],["--max-app-accounts","DM_MAX_APP_ACCOUNTS",5,"number"],["--app-accounts-user-attribute","DM_APP_ACCOUNTS_USER_ATTRIBUTE",""],["--applicative-account-attribute","DM_APPLICATIVE_ACCOUNT_ATTRIBUTES",["objectClass","cn","sn","givenName","displayName","description"],"array","--applicative-account-attributes","identifiers"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes","identifiers"],["--trash-base","DM_TRASH_BASE",""],["--trash-watched-bases","DM_TRASH_WATCHED_BASES",""],["--trash-add-metadata","DM_TRASH_ADD_METADATA","true"],["--trash-auto-create","DM_TRASH_AUTO_CREATE","true"],["--llng-ini","DM_LLNG_INI","/etc/lemonldap-ng/lemonldap-ng.ini"],["--llng-username-header","DM_LLNG_USERNAME_HEADER",""],["--auth-path-prefix","DM_AUTH_PATH_PREFIX",[],"array","--auth-path-prefixes"],["--auth-token","DM_AUTH_TOKENS",[],"array","--auth-tokens"],["--auth-fake-user","DM_AUTH_FAKE_USER",""],["--auth-totp","DM_AUTH_TOTP",[],"array","--auth-totps","phrases"],["--auth-totp-window","DM_AUTH_TOTP_WINDOW",1,"number"],["--auth-totp-step","DM_AUTH_TOTP_STEP",30,"number"],["--auth-hmac","DM_AUTH_HMAC",[],"array","--auth-hmacs","phrases"],["--auth-hmac-window","DM_AUTH_HMAC_WINDOW",12e4,"number"],["--authz-per-route","DM_AUTHZ_PER_ROUTES",[],"array","--authz-per-routes"],["--authz-unresolved-user","DM_AUTHZ_UNRESOLVED_USER","deny"],["--authz-identity","DM_AUTHZ_IDENTITY","req.user"],["--authz-for","DM_AUTHZ_FOR",[],"array",null,"identifiers"],["--authz-combine","DM_AUTHZ_COMBINE",!1,"boolean"],["--authz-scope-source","DM_AUTHZ_SCOPE_SOURCE",""],["--authz-filter-attached-entries","DM_AUTHZ_FILTER_ATTACHED_ENTRIES",!1,"boolean"],["--authz-transit-branch","DM_AUTHZ_TRANSIT_BRANCH",""],["--authz-transit-origin-attribute","DM_AUTHZ_TRANSIT_ORIGIN_ATTRIBUTE",""],["--authz-per-branch-config","DM_AUTHZ_PER_BRANCH_CONFIG",{default:{read:!0,write:!1,delete:!1}},"json"],["--authz-per-branch-cache-ttl","DM_AUTHZ_PER_BRANCH_CACHE_TTL",60,"number"],["--authz-dynamic-bypass","DM_AUTHZ_DYNAMIC_BYPASS",[],"array",null,"identifiers"],["--authz-dynamic-base","DM_AUTHZ_DYNAMIC_BASE",""],["--authz-dynamic-cache-ttl","DM_AUTHZ_DYNAMIC_CACHE_TTL",60,"number"],["--authz-dynamic-token-attribute","DM_AUTHZ_DYNAMIC_TOKEN_ATTRIBUTE","userPassword"],["--authz-dynamic-config-attribute","DM_AUTHZ_DYNAMIC_CONFIG_ATTRIBUTE","description"],["--authz-dynamic-tenant-attribute","DM_AUTHZ_DYNAMIC_TENANT_ATTRIBUTE","cn"],["--authz-dynamic-reload-endpoint","DM_AUTHZ_DYNAMIC_RELOAD_ENDPOINT",!1,"boolean"],["--authz-local-admin-attribute","DM_AUTHZ_LOCAL_ADMIN_ATTRIBUTE","twakeLocalAdminLink"],["--authz-global-branch","DM_AUTHZ_GLOBAL_BRANCHES",[],"array","--authz-global-branches","dns"],["--authz-global-write-branch","DM_AUTHZ_GLOBAL_WRITE_BRANCHES",[],"array","--authz-global-write-branches","dns"],["--authz-global-organization","DM_AUTHZ_GLOBAL_ORGANIZATION",""],["--oidc-server","DM_OIDC_SERVER",""],["--oidc-username-claim","DM_OIDC_USERNAME_CLAIM","sub"],["--oidc-client-id","DM_OIDC_CLIENT_ID",""],["--oidc-client-secret","DM_OIDC_CLIENT_SECRET",""],["--base-url","DM_BASE_URL",""],["--rate-limit-window-ms","DM_RATE_LIMIT_WINDOW_MS",9e5,"number"],["--rate-limit-max","DM_RATE_LIMIT_MAX",100,"number"],["--crowdsec-url","DM_CROWDSEC_URL","http://localhost:8080/v1/decisions"],["--crowdsec-api-key","DM_CROWDSEC_API_KEY",""],["--crowdsec-cache-ttl","DM_CROWDSEC_CACHE_TTL",60,"number"],["--trusted-proxy","DM_TRUSTED_PROXIES",[],"array","--trusted-proxies","identifiers"],["--trusted-proxy-auth-header","DM_TRUSTED_PROXY_AUTH_HEADER","Auth-User"],["--ppolicy-default-dn","DM_PPOLICY_DEFAULT_DN",""],["--ppolicy-base","DM_PPOLICY_BASE",""],["--ppolicy-warn-days","DM_PPOLICY_WARN_DAYS",14,"number"],["--ppolicy-validate-complexity","DM_PPOLICY_VALIDATE_COMPLEXITY",!1,"boolean"],["--ppolicy-min-length","DM_PPOLICY_MIN_LENGTH",12,"number"],["--ppolicy-require-uppercase","DM_PPOLICY_REQUIRE_UPPERCASE",!0,"boolean"],["--ppolicy-require-lowercase","DM_PPOLICY_REQUIRE_LOWERCASE",!0,"boolean"],["--ppolicy-require-digit","DM_PPOLICY_REQUIRE_DIGIT",!0,"boolean"],["--ppolicy-require-special","DM_PPOLICY_REQUIRE_SPECIAL",!0,"boolean"],["--ldap-users-base","DM_LDAP_USERS_BASE",""],["--scim-prefix","DM_SCIM_PREFIX","/scim/v2"],["--scim-user-base","DM_SCIM_USER_BASE",""],["--scim-group-base","DM_SCIM_GROUP_BASE",""],["--scim-user-base-template","DM_SCIM_USER_BASE_TEMPLATE",""],["--scim-group-base-template","DM_SCIM_GROUP_BASE_TEMPLATE",""],["--scim-base-map","DM_SCIM_BASE_MAP",""],["--scim-user-base-header","DM_SCIM_USER_BASE_HEADER",""],["--scim-group-base-header","DM_SCIM_GROUP_BASE_HEADER",""],["--scim-base-header-root","DM_SCIM_BASE_HEADER_ROOT",""],["--scim-user-object-class","DM_SCIM_USER_OBJECT_CLASSES",["top","inetOrgPerson","organizationalPerson","person"],"array","--scim-user-object-classes","identifiers"],["--scim-user-rdn-attribute","DM_SCIM_USER_RDN_ATTRIBUTE","uid"],["--scim-user-lock-attribute","DM_SCIM_USER_LOCK_ATTRIBUTE","pwdAccountLockedTime"],["--scim-user-lock-value","DM_SCIM_USER_LOCK_VALUE",""],["--scim-group-object-class","DM_SCIM_GROUP_OBJECT_CLASSES",["top","groupOfNames"],"array","--scim-group-object-classes","identifiers"],["--scim-group-rdn-attribute","DM_SCIM_GROUP_RDN_ATTRIBUTE","cn"],["--scim-group-external-id-attribute","DM_SCIM_GROUP_EXTERNAL_ID_ATTRIBUTE",""],["--scim-id-attribute","DM_SCIM_ID_ATTRIBUTE","rdn"],["--scim-user-mapping","DM_SCIM_USER_MAPPING",""],["--scim-group-mapping","DM_SCIM_GROUP_MAPPING",""],["--scim-max-results","DM_SCIM_MAX_RESULTS",200,"number"],["--scim-max-scanned","DM_SCIM_MAX_SCANNED",1e4,"number"],["--scim-bulk-max-operations","DM_SCIM_BULK_MAX_OPERATIONS",100,"number"],["--scim-bulk-max-payload-size","DM_SCIM_BULK_MAX_PAYLOAD_SIZE",1048576,"number"],["--scim-etag","DM_SCIM_ETAG",!1,"boolean"],["--scim-base-url","DM_SCIM_BASE_URL",""]];export{t as STATIC_CSP,E as STATIC_FRAME_OPTIONS,r as default};
@@ -1 +1 @@
1
- import e from"fs";import{join as t,dirname as r,resolve as s,relative as a,isAbsolute as o}from"path";import{fileURLToPath as i}from"url";import n from"../config/args.js";import{transformSchemas as c}from"./utils.js";const h=t(r(i(import.meta.url)),"..","..","static","schemas"),l="ldap-rest:",isPlainObject=e=>"object"==typeof e&&null!==e&&!Array.isArray(e);function mergeSchema(e,t){if(!isPlainObject(t))return t;const r=isPlainObject(e)?{...e}:{};for(const[e,s]of Object.entries(t))null!==s?Object.defineProperty(r,e,{value:mergeSchema(Object.prototype.hasOwnProperty.call(r,e)?r[e]:void 0,s),enumerable:!0,writable:!0,configurable:!0}):delete r[e];return r}function resolveSchemaBase(e,t){if(!t.startsWith(l))return s(r(e),t);const i=s(h,t.slice(10)),n=a(h,i);if(!n||n.startsWith("..")||o(n))throw new Error(`${e}: "${t}" is not a shipped schema`);return i}class SchemaChain{constructor(e){this.options=e,this.files=[],this.realPaths=[],this.layers=[]}readError(e,t){return this.files.length?new Error(`${this.files[this.files.length-1]} extends ${e}, which cannot be read: ${t.message}`):t}add(e,t,r){if(this.realPaths.includes(t))throw new Error(`"extends" loop: ${[...this.files,e].join(" -> ")}`);this.files.push(e),this.realPaths.push(t);const s=this.options.config?c(r,this.options.config):r;let a;try{a=JSON.parse(s)}catch(t){throw new Error(`${e}: ${t.message}`)}if(!isPlainObject(a)||!("extends"in a))return void this.layers.push(a);const{extends:o,...i}=a;if("string"!=typeof o||!o)throw new Error(`${e}: "extends" must be the path of a schema`);return this.layers.push(i),resolveSchemaBase(e,o)}result(){return this.layers.reduceRight((e,t)=>mergeSchema(e,t))}}function loadSchemaFile(t,r={}){const s=new SchemaChain(r);for(let r=t;r;){let t;try{t=e.readFileSync(r,"utf8")}catch(e){throw s.readError(r,e)}r=s.add(r,e.realpathSync(r),t)}return s.result()}async function loadSchemaFileAsync(t,r={}){const s=new SchemaChain(r);for(let r=t;r;){let t;try{t=await e.promises.readFile(r,"utf8")}catch(e){throw s.readError(r,e)}r=s.add(r,await e.promises.realpath(r),t)}return s.result()}function schemaUrl(e,t){const r=t.indexOf("/schemas/");if(-1!==r)return`/${e.static_name||"static"}${t.substring(r)}`}const f=[["group_schema","--group-schema"],["organization_schema","--organization-schema"],["ldap_flat_schema","--ldap-flat-schema"]];function configuredSchemaFiles(e){const t=[];for(const[r,a]of f){const o=[n.find(e=>e[0]===a)?.[2]].flat().filter(e=>"string"==typeof e&&!!e).map(e=>s(e));for(const a of[e[r]].flat())"string"==typeof a&&a&&!o.includes(s(a))&&t.push(a)}return t}export{l as SHIPPED_SCHEMA_PREFIX,configuredSchemaFiles,loadSchemaFile,loadSchemaFileAsync,mergeSchema,resolveSchemaBase,schemaUrl,h as shippedSchemasPath};
1
+ import e from"fs";import{join as t,dirname as r,resolve as s,relative as a,isAbsolute as o}from"path";import{fileURLToPath as i}from"url";import n from"../config/args.js";import{transformSchemas as c}from"./utils.js";const h=t(r(i(import.meta.url)),"..","..","static","schemas"),l="ldap-rest:",isPlainObject=e=>"object"==typeof e&&null!==e&&!Array.isArray(e);function mergeSchema(e,t){if(!isPlainObject(t))return t;const r=isPlainObject(e)?{...e}:{};for(const[e,s]of Object.entries(t))null!==s?Object.defineProperty(r,e,{value:mergeSchema(Object.prototype.hasOwnProperty.call(r,e)?r[e]:void 0,s),enumerable:!0,writable:!0,configurable:!0}):delete r[e];return r}function resolveSchemaBase(e,t){if(!t.startsWith(l))return s(r(e),t);const i=s(h,t.slice(10)),n=a(h,i);if(!n||n.startsWith("..")||o(n))throw new Error(`${e}: "${t}" is not a shipped schema`);return i}class SchemaChain{constructor(e){this.options=e,this.files=[],this.realPaths=[],this.layers=[]}readError(e,t){return this.files.length?new Error(`${this.files[this.files.length-1]} extends ${e}, which cannot be read: ${t.message}`):t}add(e,t,r){if(this.realPaths.includes(t))throw new Error(`"extends" loop: ${[...this.files,e].join(" -> ")}`);this.files.push(e),this.realPaths.push(t);const s=this.options.config?c(r,this.options.config):r;let a;try{a=JSON.parse(s)}catch(t){throw new Error(`${e}: ${t.message}`)}if(!isPlainObject(a)||!("extends"in a))return void this.layers.push(a);const{extends:o,...i}=a;if("string"!=typeof o||!o)throw new Error(`${e}: "extends" must be the path of a schema`);return this.layers.push(i),resolveSchemaBase(e,o)}result(){return this.layers.reduceRight((e,t)=>mergeSchema(e,t))}}function loadSchemaFile(t,r={}){const s=new SchemaChain(r);for(let r=t;r;){let t;try{t=e.readFileSync(r,"utf8")}catch(e){throw s.readError(r,e)}r=s.add(r,e.realpathSync(r),t)}return s.result()}async function loadSchemaFileAsync(t,r={}){const s=new SchemaChain(r);for(let r=t;r;){let t;try{t=await e.promises.readFile(r,"utf8")}catch(e){throw s.readError(r,e)}r=s.add(r,await e.promises.realpath(r),t)}return s.result()}function staticName(e){const t=e.static_name,r=("string"==typeof t?t:"").trim();let s=0,a=r.length;for(;s<a&&"/"===r[s];)s++;for(;a>s&&"/"===r[a-1];)a--;return r.slice(s,a)||"static"}function schemaUrl(e,t){const r=t.indexOf("/schemas/");if(-1!==r)return`/${staticName(e)}${t.substring(r)}`}const f=[["group_schema","--group-schema"],["organization_schema","--organization-schema"],["ldap_flat_schema","--ldap-flat-schema"]];function configuredSchemaFiles(e){const t=[];for(const[r,a]of f){const o=[n.find(e=>e[0]===a)?.[2]].flat().filter(e=>"string"==typeof e&&!!e).map(e=>s(e));for(const a of[e[r]].flat())"string"==typeof a&&a&&!o.includes(s(a))&&t.push(a)}return t}export{l as SHIPPED_SCHEMA_PREFIX,configuredSchemaFiles,loadSchemaFile,loadSchemaFileAsync,mergeSchema,resolveSchemaBase,schemaUrl,h as shippedSchemasPath,staticName};
@@ -1 +1 @@
1
- import{fileURLToPath as s}from"url";import{join as e,dirname as i}from"path";import t from"../../abstract/plugin.js";import{setChangeSource as o}from"../../lib/changeContext.js";import{jsonBodyParser as r}from"../../lib/rawBody.js";import{BaseResolver as a}from"./baseResolver.js";import{ScimUsers as c}from"./users.js";import{ScimGroups as n}from"./groups.js";import{ScimBulk as p}from"./bulk.js";import{ScimDiscovery as m}from"./discovery.js";import{SCIM_CONTENT_TYPE as u,scimAsyncHandler as d,writeScimError as h}from"./errors.js";export{writeScimErrorFromException}from"./errors.js";import{projectResource as l,assertProjection as g,projectList as f}from"./projection.js";function scimBodyParser(s){return r({type:["application/json","application/scim+json"],limit:`${s.scim_bulk_max_payload_size||1048576}b`})}class Scim extends t{constructor(t){super(t),this.name="scim",this.roles=["api","configurable"],this.ldap=t.ldap,this.scimPrefix=this.config.scim_prefix||"/scim/v2",this.baseResolver=new a(this.config),this.users=new c({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,hooks:this.registeredHooks}),this.groups=new n({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,users:this.users,hooks:this.registeredHooks}),this.bulk=new p({config:this.config,logger:this.logger,users:this.users,groups:this.groups,scimPrefix:this.scimPrefix,hooks:this.registeredHooks});const o=this.config.schemas_path||e(i(s(import.meta.url)),"..","..","..","static","schemas");this.discovery=new m({config:this.config,schemaDir:e(o,"scim"),scimPrefix:this.scimPrefix,loadedPlugins:t.loadedPlugins})}scimJson(s,e,i){s.status(e).type(u).json(i)}scimResource(s,e,i,t,o={}){const r=i.id?`${this.scimPrefix}/${t}/${encodeURIComponent(i.id)}`:void 0,a=(this.config.scim_base_url||"")&&i.meta?.location?i.meta.location:r;a&&s.set("Location",a),this.scimJson(s,e,l(i,o))}parseProjection(s){const toCsv=s=>{if("string"!=typeof s)return;const e=s.split(",").map(s=>s.trim()).filter(Boolean);return e.length>0?e:void 0},e={attributes:toCsv(s.query.attributes),excludedAttributes:toCsv(s.query.excludedAttributes)};return g(e),e}readScimBody(s){const e=String(s.headers["content-type"]||"");if(e.startsWith("application/scim+json")||e.startsWith("application/json"))return s.body}api(s){const e=this.scimPrefix;this.server.registerBodyParser(e,scimBodyParser(this.config)),s.use(e,(s,e,i)=>{o(s,"scim"),i()}),s.use(e,d((s,e,i)=>{this.parseProjection(s),i()})),s.get(`${e}/Users`,d(async(s,e)=>{const i=this.parseListQuery(s);g(i);const t=await this.users.list(s,i);this.scimJson(e,200,f(t,i))})),s.get(`${e}/Users/:id`,d(async(s,e)=>{const i=this.parseProjection(s),t=await this.users.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,l(t,i))})),s.post(`${e}/Users`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.users.create(s,i);this.scimResource(e,201,t,"Users",this.parseProjection(s))})),s.put(`${e}/Users/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.users.replace(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Users",this.parseProjection(s))})),s.patch(`${e}/Users/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.users.patch(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Users",this.parseProjection(s))})),s.delete(`${e}/Users/:id`,d(async(s,e)=>{await this.users.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.get(`${e}/Groups`,d(async(s,e)=>{const i=this.parseListQuery(s);g(i);const t=await this.groups.list(s,i);this.scimJson(e,200,f(t,i))})),s.get(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.parseProjection(s),t=await this.groups.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,l(t,i))})),s.post(`${e}/Groups`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.groups.create(s,i);this.scimResource(e,201,t,"Groups",this.parseProjection(s))})),s.put(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.groups.replace(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Groups",this.parseProjection(s))})),s.patch(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.groups.patch(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Groups",this.parseProjection(s))})),s.delete(`${e}/Groups/:id`,d(async(s,e)=>{await this.groups.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.post(`${e}/Bulk`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=this.config.scim_bulk_max_payload_size||1048576,o=JSON.stringify(i).length;if(o>t)return h(e,413,`Bulk payload too large: ${o} > ${t}`,"invalidValue");const r=await this.bulk.execute(s,i);this.scimJson(e,200,r)})),s.get(`${e}/ServiceProviderConfig`,d((s,e)=>{this.scimJson(e,200,this.discovery.serviceProviderConfig(s))})),s.get(`${e}/ResourceTypes`,d((s,e)=>{const i=this.discovery.resourceTypes(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:i.length,Resources:i,startIndex:1,itemsPerPage:i.length})})),s.get(`${e}/ResourceTypes/:name`,d((s,e)=>{const i=this.discovery.resourceType(s.params.name,s);if(!i)return h(e,404,"ResourceType not found");this.scimJson(e,200,i)})),s.get(`${e}/Schemas`,d((s,e)=>{const i=this.discovery.schemas(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:i.length,Resources:i,startIndex:1,itemsPerPage:i.length})})),s.get(`${e}/Schemas/:id`,d((s,e)=>{const i=this.discovery.schema(decodeURIComponent(s.params.id),s);if(!i)return h(e,404,"Schema not found");this.scimJson(e,200,i)}))}parseListQuery(s){const e=s.query,toInt=s=>{if("string"!=typeof s)return;const e=parseInt(s,10);return Number.isFinite(e)?e:void 0},toCsv=s=>{if("string"==typeof s)return s.split(",").map(s=>s.trim()).filter(Boolean)},i="string"==typeof e.sortOrder?e.sortOrder:void 0;return{filter:"string"==typeof e.filter?e.filter:void 0,startIndex:toInt(e.startIndex),count:toInt(e.count),attributes:toCsv(e.attributes),excludedAttributes:toCsv(e.excludedAttributes),sortBy:"string"==typeof e.sortBy?e.sortBy:void 0,sortOrder:"ascending"===i||"descending"===i?i:void 0}}getConfigApiData(){const s=this.scimPrefix;let e;if(this.server.loadedPlugins.static){const s=this.config.static_name||"static";e={user:`/${s}/schemas/scim/User.json`,group:`/${s}/schemas/scim/Group.json`,defaultMapping:`/${s}/schemas/scim/default-mapping.json`}}const i=this.users.userMapping,t=this.groups.groupMapping,o=i.entries.map(s=>s.scim).concat(["id","active"]),r=t.entries.map(s=>s.scim).concat(["id"]);return{enabled:!0,version:"2.0",prefix:s,endpoints:{users:`${s}/Users`,user:`${s}/Users/:id`,groups:`${s}/Groups`,group:`${s}/Groups/:id`,bulk:`${s}/Bulk`,serviceProviderConfig:`${s}/ServiceProviderConfig`,resourceTypes:`${s}/ResourceTypes`,schemas:`${s}/Schemas`},schemaUrls:e,capabilities:{patch:!0,bulk:!0,filter:!0,sort:!0,etag:Boolean(this.config.scim_etag),changePassword:!1,maxResults:this.config.scim_max_results||200,maxScanned:this.config.scim_max_scanned||1e4,bulkMaxOperations:this.config.scim_bulk_max_operations||100,bulkMaxPayloadSize:this.config.scim_bulk_max_payload_size||1048576},resourceTypes:[{id:"User",endpoint:`${s}/Users`,schema:"urn:ietf:params:scim:schemas:core:2.0:User",rdnAttribute:this.config.scim_user_rdn_attribute||"uid",objectClass:this.config.scim_user_object_class,filterableAttributes:o,mapping:i.entries},{id:"Group",endpoint:`${s}/Groups`,schema:"urn:ietf:params:scim:schemas:core:2.0:Group",rdnAttribute:this.config.scim_group_rdn_attribute||"cn",objectClass:this.config.scim_group_object_class,filterableAttributes:r,mapping:t.entries}],idStrategy:this.config.scim_id_attribute||"rdn",baseResolution:{userBaseTemplate:this.config.scim_user_base_template||void 0,groupBaseTemplate:this.config.scim_group_base_template||void 0,hasBaseMap:Boolean(this.config.scim_base_map)}}}}export{Scim as default,scimBodyParser};
1
+ import{fileURLToPath as s}from"url";import{join as e,dirname as i}from"path";import t from"../../abstract/plugin.js";import{setChangeSource as o}from"../../lib/changeContext.js";import{jsonBodyParser as r}from"../../lib/rawBody.js";import{BaseResolver as a}from"./baseResolver.js";import{ScimUsers as n}from"./users.js";import{ScimGroups as c}from"./groups.js";import{ScimBulk as p}from"./bulk.js";import{ScimDiscovery as m}from"./discovery.js";import{SCIM_CONTENT_TYPE as u,scimAsyncHandler as d,writeScimError as h}from"./errors.js";export{writeScimErrorFromException}from"./errors.js";import{projectResource as l,assertProjection as g,projectList as f}from"./projection.js";const isStaticFiles=s=>"function"==typeof s.urlOf&&"function"==typeof s.servedPath;function scimBodyParser(s){return r({type:["application/json","application/scim+json"],limit:`${s.scim_bulk_max_payload_size||1048576}b`})}class Scim extends t{constructor(t){super(t),this.name="scim",this.roles=["api","configurable"],this.ldap=t.ldap,this.scimPrefix=this.config.scim_prefix||"/scim/v2",this.baseResolver=new a(this.config),this.users=new n({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,hooks:this.registeredHooks}),this.groups=new c({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,users:this.users,hooks:this.registeredHooks}),this.bulk=new p({config:this.config,logger:this.logger,users:this.users,groups:this.groups,scimPrefix:this.scimPrefix,hooks:this.registeredHooks});const o=this.config.schemas_path||e(i(s(import.meta.url)),"..","..","..","static","schemas");this.scimSchemaDir=e(o,"scim"),this.discovery=new m({config:this.config,schemaDir:this.scimSchemaDir,scimPrefix:this.scimPrefix,loadedPlugins:t.loadedPlugins})}scimJson(s,e,i){s.status(e).type(u).json(i)}scimResource(s,e,i,t,o={}){const r=i.id?`${this.scimPrefix}/${t}/${encodeURIComponent(i.id)}`:void 0,a=(this.config.scim_base_url||"")&&i.meta?.location?i.meta.location:r;a&&s.set("Location",a),this.scimJson(s,e,l(i,o))}parseProjection(s){const toCsv=s=>{if("string"!=typeof s)return;const e=s.split(",").map(s=>s.trim()).filter(Boolean);return e.length>0?e:void 0},e={attributes:toCsv(s.query.attributes),excludedAttributes:toCsv(s.query.excludedAttributes)};return g(e),e}readScimBody(s){const e=String(s.headers["content-type"]||"");if(e.startsWith("application/scim+json")||e.startsWith("application/json"))return s.body}api(s){const e=this.scimPrefix;this.server.registerBodyParser(e,scimBodyParser(this.config)),s.use(e,(s,e,i)=>{o(s,"scim"),i()}),s.use(e,d((s,e,i)=>{this.parseProjection(s),i()})),s.get(`${e}/Users`,d(async(s,e)=>{const i=this.parseListQuery(s);g(i);const t=await this.users.list(s,i);this.scimJson(e,200,f(t,i))})),s.get(`${e}/Users/:id`,d(async(s,e)=>{const i=this.parseProjection(s),t=await this.users.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,l(t,i))})),s.post(`${e}/Users`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.users.create(s,i);this.scimResource(e,201,t,"Users",this.parseProjection(s))})),s.put(`${e}/Users/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.users.replace(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Users",this.parseProjection(s))})),s.patch(`${e}/Users/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.users.patch(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Users",this.parseProjection(s))})),s.delete(`${e}/Users/:id`,d(async(s,e)=>{await this.users.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.get(`${e}/Groups`,d(async(s,e)=>{const i=this.parseListQuery(s);g(i);const t=await this.groups.list(s,i);this.scimJson(e,200,f(t,i))})),s.get(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.parseProjection(s),t=await this.groups.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,l(t,i))})),s.post(`${e}/Groups`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.groups.create(s,i);this.scimResource(e,201,t,"Groups",this.parseProjection(s))})),s.put(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.groups.replace(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Groups",this.parseProjection(s))})),s.patch(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.groups.patch(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Groups",this.parseProjection(s))})),s.delete(`${e}/Groups/:id`,d(async(s,e)=>{await this.groups.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.post(`${e}/Bulk`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=this.config.scim_bulk_max_payload_size||1048576,o=JSON.stringify(i).length;if(o>t)return h(e,413,`Bulk payload too large: ${o} > ${t}`,"invalidValue");const r=await this.bulk.execute(s,i);this.scimJson(e,200,r)})),s.get(`${e}/ServiceProviderConfig`,d((s,e)=>{this.scimJson(e,200,this.discovery.serviceProviderConfig(s))})),s.get(`${e}/ResourceTypes`,d((s,e)=>{const i=this.discovery.resourceTypes(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:i.length,Resources:i,startIndex:1,itemsPerPage:i.length})})),s.get(`${e}/ResourceTypes/:name`,d((s,e)=>{const i=this.discovery.resourceType(s.params.name,s);if(!i)return h(e,404,"ResourceType not found");this.scimJson(e,200,i)})),s.get(`${e}/Schemas`,d((s,e)=>{const i=this.discovery.schemas(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:i.length,Resources:i,startIndex:1,itemsPerPage:i.length})})),s.get(`${e}/Schemas/:id`,d((s,e)=>{const i=this.discovery.schema(decodeURIComponent(s.params.id),s);if(!i)return h(e,404,"Schema not found");this.scimJson(e,200,i)}))}parseListQuery(s){const e=s.query,toInt=s=>{if("string"!=typeof s)return;const e=parseInt(s,10);return Number.isFinite(e)?e:void 0},toCsv=s=>{if("string"==typeof s)return s.split(",").map(s=>s.trim()).filter(Boolean)},i="string"==typeof e.sortOrder?e.sortOrder:void 0;return{filter:"string"==typeof e.filter?e.filter:void 0,startIndex:toInt(e.startIndex),count:toInt(e.count),attributes:toCsv(e.attributes),excludedAttributes:toCsv(e.excludedAttributes),sortBy:"string"==typeof e.sortBy?e.sortBy:void 0,sortOrder:"ascending"===i||"descending"===i?i:void 0}}getConfigApiData(){const s=this.scimPrefix,i=["User.json","Group.json","default-mapping.json"].map(s=>e(this.scimSchemaDir,s)),t=new Set,o=[];for(const s of Object.values(this.server.loadedPlugins)){if(!isStaticFiles(s))continue;const e=s.servedPath().toLowerCase();t.has(e)||(t.add(e),o.push(s))}let r;o.sort((s,e)=>Number("static"===e.name)-Number("static"===s.name));for(const s of o){const[e,t,o]=i.map(e=>s.urlOf(e));if(e&&t){r={user:e,group:t,...o?{defaultMapping:o}:{}};break}}const a=this.users.userMapping,n=this.groups.groupMapping,c=a.entries.map(s=>s.scim).concat(["id","active"]),p=n.entries.map(s=>s.scim).concat(["id"]);return{enabled:!0,version:"2.0",prefix:s,endpoints:{users:`${s}/Users`,user:`${s}/Users/:id`,groups:`${s}/Groups`,group:`${s}/Groups/:id`,bulk:`${s}/Bulk`,serviceProviderConfig:`${s}/ServiceProviderConfig`,resourceTypes:`${s}/ResourceTypes`,schemas:`${s}/Schemas`},schemaUrls:r,capabilities:{patch:!0,bulk:!0,filter:!0,sort:!0,etag:Boolean(this.config.scim_etag),changePassword:!1,maxResults:this.config.scim_max_results||200,maxScanned:this.config.scim_max_scanned||1e4,bulkMaxOperations:this.config.scim_bulk_max_operations||100,bulkMaxPayloadSize:this.config.scim_bulk_max_payload_size||1048576},resourceTypes:[{id:"User",endpoint:`${s}/Users`,schema:"urn:ietf:params:scim:schemas:core:2.0:User",rdnAttribute:this.config.scim_user_rdn_attribute||"uid",objectClass:this.config.scim_user_object_class,filterableAttributes:c,mapping:a.entries},{id:"Group",endpoint:`${s}/Groups`,schema:"urn:ietf:params:scim:schemas:core:2.0:Group",rdnAttribute:this.config.scim_group_rdn_attribute||"cn",objectClass:this.config.scim_group_object_class,filterableAttributes:p,mapping:n.entries}],idStrategy:this.config.scim_id_attribute||"rdn",baseResolution:{userBaseTemplate:this.config.scim_user_base_template||void 0,groupBaseTemplate:this.config.scim_group_base_template||void 0,hasBaseMap:Boolean(this.config.scim_base_map)}}}}export{Scim as default,scimBodyParser};
@@ -1 +1 @@
1
- import t from"fs";import{resolve as s,join as e}from"path";import n from"express";import i from"../abstract/plugin.js";import{notFound as a,serverError as r}from"../lib/expressFormatedResponses.js";import{configuredSchemaFiles as c,schemaUrl as o,loadSchemaFileAsync as h}from"../lib/schemaFile.js";class Static extends i{constructor(){super(...arguments),this.name="static",this.roles=["api","configurable"]}api(s){const e=this.config.static_path;if(!e)throw new Error("--static-path is not defined");try{if(!t.statSync(e).isDirectory())throw new Error(`${e} isn't a directory`);t.accessSync(e,t.constants.R_OK)}catch(t){throw new Error(`Bad directory ${e}: ${t}`)}const i=this.schemaServer(e);s.get(`/${this.config.static_name}/schemas/:name`,(t,s,e)=>i([t.params.name],t,s,e)),s.get(`/${this.config.static_name}/schemas/:dir/:name`,(t,s,e)=>i([t.params.dir,t.params.name],t,s,e)),s.get(`/${this.config.static_name}/schemas/*path`,(t,s,e)=>i(t.params.path,t,s,e)),s.use(`/${this.config.static_name}`,n.static(e))}schemaServer(t){const n=this.configuredSchemas(t),i=s(e(t,"schemas"));return(t,a,r,c)=>{const o=n.get((t=>{try{return decodeURIComponent(t)}catch{return t}})(a.path));if(o)return this.sendSchema(o,r);if(!t[t.length-1].endsWith(".json"))return c();if(!t.every(t=>/^[\w-]+(\.[\w-]+)*$/.test(t)))return void r.status(400).send("Invalid schema name");const h=s(e(i,...t));h.startsWith(i+"/")?this.sendSchema(h,r):r.status(403).send("Access denied")}}configuredSchemas(t){const n=new Map;for(const i of c(this.config)){const a=o(this.config,i);if(!a)continue;const r=e(t,a.substring(a.indexOf("/schemas/")));if(s(r)===s(i))continue;const c=n.get(a);c&&s(c)!==s(i)?this.logger.warn(`${i} is not served: ${c} already has its URL ${a}`):n.set(a,i)}return n}sendSchema(t,s){h(t,{config:this.config}).then(t=>s.type("json").send(JSON.stringify(t)),t=>{if(t.code)return a(s,"Schema not found");r(s,t)})}getConfigApiData(){const t=`/${this.config.static_name||"static"}`;return{enabled:!0,staticPath:t,endpoints:{schema:`${t}/schemas/:name`,schemaInSubdir:`${t}/schemas/:dir/:name`,files:`${t}/*`}}}}export{Static as default};
1
+ import t from"fs";import{resolve as e,join as s,relative as r,sep as n,isAbsolute as i}from"path";import{validateHeaderValue as o}from"http";import a from"express";import c from"../abstract/plugin.js";import{STATIC_CSP as h,STATIC_FRAME_OPTIONS as f}from"../config/args.js";import{notFound as m,serverError as p}from"../lib/expressFormatedResponses.js";import{staticName as u,configuredSchemaFiles as d,schemaUrl as g,loadSchemaFileAsync as l}from"../lib/schemaFile.js";const headerValue=(t,e,s,r)=>{if(!1===s)return;if(null!=s&&"string"!=typeof s)throw new Error(`${t}: a text is expected, or false for none`);const n=("string"==typeof s?s:"").replace(/\s+/g," ").trim();if(!n)return r;if("off"!==n.toLowerCase()){try{o(e,n)}catch{throw new Error(`${t}: "${n}" cannot be sent as ${e}`)}return n}};class Static extends c{constructor(){super(...arguments),this.name="static",this.roles=["api","configurable"],this.configured=new Map}api(e){const s=this.config.static_path;if(!s)throw new Error("--static-path is not defined");try{if(!t.statSync(s).isDirectory())throw new Error(`${s} isn't a directory`);t.accessSync(s,t.constants.R_OK)}catch(t){throw new Error(`Bad directory ${s}: ${t}`)}const r=this.config.static_name;if(null!=r&&"string"!=typeof r)throw new Error("--static-name: a text is expected, such as static");const n=u(this.config);if(n.split("/").some(t=>!t||/^\.+$/.test(t)||/[^\x21-\x7e]|[:*?+!()[\]{}\\#%"<>|^`]/.test(t)))throw new Error(`--static-name: "${n}" is not a path to serve under, such as static`);const i=`/${n}`,o=this.schemaServer(s),c=headerValue("--static-csp","Content-Security-Policy",this.config.static_csp,h),m=headerValue("--static-frame-options","X-Frame-Options",this.config.static_frame_options,f);e.use(i,(t,e,s)=>{c&&e.setHeader("Content-Security-Policy",c),m&&e.setHeader("X-Frame-Options",m),s()}),e.get(`${i}/schemas/:name`,(t,e,s)=>o([t.params.name],t,e,s)),e.get(`${i}/schemas/:dir/:name`,(t,e,s)=>o([t.params.dir,t.params.name],t,e,s)),e.get(`${i}/schemas/*path`,(t,e,s)=>o(t.params.path,t,e,s)),e.use(i,a.static(s))}schemaServer(t){const r=this.configured=this.configuredSchemas(t),n=e(s(t,"schemas"));return(t,i,o,a)=>{const c=r.get((t=>{try{return decodeURIComponent(t)}catch{return t}})(i.path));if(c)return this.sendSchema(c,o);if(!t[t.length-1].endsWith(".json"))return a();if(!t.every(t=>/^[\w-]+(\.[\w-]+)*$/.test(t)))return void o.status(400).send("Invalid schema name");const h=e(s(n,...t));h.startsWith(n+"/")?this.sendSchema(h,o):o.status(403).send("Access denied")}}configuredSchemas(t){const r=new Map;for(const n of d(this.config)){const i=g(this.config,n);if(!i)continue;const o=s(t,i.substring(i.indexOf("/schemas/")));if(e(o)===e(n))continue;const a=r.get(i);a&&e(a)!==e(n)?this.logger.warn(`${n} is not served: ${a} already has its URL ${i}`):r.set(i,n)}return r}sendSchema(t,e){l(t,{config:this.config}).then(t=>e.type("json").send(JSON.stringify(t)),t=>{if(t.code)return m(e,"Schema not found");p(e,t)})}urlOf(s){const o=e(s);let a;try{if(!t.statSync(o).isFile())return;a=t.realpathSync(o)}catch{return}const same=s=>{if(e(s)===o)return!0;try{return t.realpathSync(s)===a}catch{return!1}},c=this.servedPath(),url=t=>`${c}/${t.map(encodeURIComponent).join("/")}`;for(const[t,e]of this.configured)if(same(e)){const e=t.slice(c.length+1).split("/");if(e.some(t=>["",".",".."].includes(t)))continue;return url(e)}const h=this.config.static_path;if(!h)return;let f;try{f=t.realpathSync(h)}catch{return}const m=[r(e(h),o),r(f,a)].find(t=>!!t&&".."!==t&&!t.startsWith(`..${n}`)&&!i(t));if(!m)return;const p=m.split(n);return p.some(t=>t.startsWith("."))||"schemas"===p[0].toLowerCase()&&"schemas"!==p[0]&&m.endsWith(".json")||"schemas"===p[0]&&m.endsWith(".json")&&!p.slice(1).every(t=>/^[\w-]+(\.[\w-]+)*$/.test(t))||this.configured.has(`${c}/${p.join("/")}`)?void 0:url(p)}servedPath(){return`/${u(this.config)}`}getConfigApiData(){const t=this.servedPath();return{enabled:!0,staticPath:t,endpoints:{schema:`${t}/schemas/:name`,schemaInSubdir:`${t}/schemas/:dir/:name`,files:`${t}/*`}}}}export{Static as default};
@@ -1,5 +1,8 @@
1
1
  import type { AttributesList } from '../lib/ldapActions';
2
2
  import type { ConfigTemplate } from '../lib/parseConfig';
3
+ /** The headers `core/static` sends unless told otherwise: no framing by another site */
4
+ export declare const STATIC_CSP = "frame-ancestors 'self'";
5
+ export declare const STATIC_FRAME_OPTIONS = "SAMEORIGIN";
3
6
  export interface BranchPermissions {
4
7
  read?: boolean;
5
8
  write?: boolean;
@@ -89,6 +92,8 @@ export interface Config {
89
92
  external_branch_class?: string[];
90
93
  static_path?: string;
91
94
  static_name?: string;
95
+ static_csp?: string;
96
+ static_frame_options?: string;
92
97
  auth_path_prefix?: string | string[];
93
98
  llng_ini?: string;
94
99
  llng_username_header?: string;
@@ -43,6 +43,16 @@ export declare function loadSchemaFile<T = unknown>(file: string, options?: Sche
43
43
  * @returns the parsed schema, merged with its bases
44
44
  */
45
45
  export declare function loadSchemaFileAsync<T = unknown>(file: string, options?: SchemaFileOptions): Promise<T>;
46
+ /**
47
+ * The path the static plugin serves under: --static-name without the
48
+ * slashes around it, and `static` when that leaves nothing, as a compose
49
+ * file's unset `${VAR:-}` gives. The plugin refuses at startup one that is
50
+ * not a path to serve under.
51
+ *
52
+ * @param config server configuration
53
+ * @returns the path, with no slash at either end
54
+ */
55
+ export declare function staticName(config: Config): string;
46
56
  /**
47
57
  * URL the static plugin serves a schema file at: its path from the first
48
58
  * `schemas` directory it is in, under --static-name
@@ -411,6 +411,8 @@ export default class Scim extends DmPlugin {
411
411
  private readonly groups;
412
412
  private readonly bulk;
413
413
  private readonly discovery;
414
+ /** Where it reads its schema files, which the static plugins may serve */
415
+ private readonly scimSchemaDir;
414
416
  private readonly baseResolver;
415
417
  constructor(server: DM);
416
418
  private scimJson;
@@ -37,6 +37,8 @@ import DmPlugin, { type Role } from '../abstract/plugin';
37
37
  export default class Static extends DmPlugin {
38
38
  name: string;
39
39
  roles: Role[];
40
+ /** The configuration's schema files it serves outside its directory, by URL */
41
+ private configured;
40
42
  api(app: Express): void;
41
43
  /**
42
44
  * The handler of the schema routes
@@ -51,6 +53,20 @@ export default class Static extends DmPlugin {
51
53
  */
52
54
  private configuredSchemas;
53
55
  private sendSchema;
56
+ /**
57
+ * The URL it serves a file at, for another plugin to announce it: under
58
+ * its own name, which a plugin override may have changed, from its
59
+ * directory or among the configuration's schema files it serves. One its
60
+ * routes would not answer with that file is none: a hidden one, which
61
+ * express.static ignores, a schema whose path the schema route refuses,
62
+ * or one whose URL a configured schema holds.
63
+ *
64
+ * @param file the file, as the other plugin reads it
65
+ * @returns its URL, or undefined when this plugin does not serve it
66
+ */
67
+ urlOf(file: string): string | undefined;
68
+ /** The path it serves under, which an earlier plugin may have taken */
69
+ servedPath(): string;
54
70
  /**
55
71
  * Provide configuration for config API
56
72
  */
@@ -662,10 +662,14 @@ See [spaces](plugins/integrations/spaces.md).
662
662
 
663
663
  #### `core/static`
664
664
 
665
- | CLI | Env | Default | Description |
666
- | --------------- | ---------------- | -------- | ---------------------- |
667
- | `--static-path` | `DM_STATIC_PATH` | `static` | Static files directory |
668
- | `--static-name` | `DM_STATIC_NAME` | `static` | URL path prefix |
665
+ | CLI | Env | Default | Description |
666
+ | ------------------------ | ------------------------- | ------------------------ | ------------------------------------------------------ |
667
+ | `--static-path` | `DM_STATIC_PATH` | `static` | Static files directory |
668
+ | `--static-name` | `DM_STATIC_NAME` | `static` | URL path prefix |
669
+ | `--static-csp` | `DM_STATIC_CSP` | `frame-ancestors 'self'` | `Content-Security-Policy` of the files; `off` for none |
670
+ | `--static-frame-options` | `DM_STATIC_FRAME_OPTIONS` | `SAMEORIGIN` | `X-Frame-Options` of the files; `off` for none |
671
+
672
+ See [security headers](plugins/utilities/static.md#security-headers).
669
673
 
670
674
  #### `core/weblogs`
671
675
 
@@ -731,8 +731,14 @@ Response shape (truncated):
731
731
 
732
732
  Notable fields:
733
733
 
734
- - `schemaUrls` — direct download URLs when `core/static` is loaded (editor UIs
735
- typically fetch them to render attribute pickers).
734
+ - `schemaUrls` — direct download URLs of the schema files SCIM reads
735
+ (`User.json` and `Group.json` under `--schemas-path`, with the
736
+ `default-mapping.json` template beside them when it is there), when a
737
+ `core/static` plugin serves them, under its own `--static-name` (editor
738
+ UIs typically fetch them to render attribute pickers). Among plugins
739
+ serving different paths, the one registered as `static` is preferred; a
740
+ path an earlier plugin already serves is left to that one. Absent when
741
+ none serves them: read it with a guard.
736
742
  - `resourceTypes[].mapping` — the live SCIM↔LDAP mapping used server-side, so
737
743
  UI builders can render filter helpers / attribute selectors without second
738
744
  round-trip.
@@ -11,20 +11,30 @@ The `static` plugin serves static files (HTML, CSS, JS, JSON, images) from a dir
11
11
  ```bash
12
12
  --plugin core/static \
13
13
  --static-path ./static \
14
- --static-prefix /static
14
+ --static-name static
15
15
  ```
16
16
 
17
17
  **Environment Variables:**
18
18
 
19
19
  ```bash
20
20
  DM_STATIC_PATH="./static"
21
- DM_STATIC_PREFIX="/static"
21
+ DM_STATIC_NAME="static"
22
22
  ```
23
23
 
24
24
  ### Parameters
25
25
 
26
26
  - `--static-path`: Directory containing static files (required)
27
- - `--static-prefix`: URL prefix for serving files (default: `/static`)
27
+ - `--static-name`: the path the files are served under, without a slash
28
+ around it (default: `static`, for `/static/…`). Slashes around it are left
29
+ out and an empty value is `static`; a segment that is empty, `.` or `..`,
30
+ or that holds what a URL encodes or Express reads as route syntax (a
31
+ space, a letter outside ASCII, `:`, `*`, `?`, `#`, `%`, brackets, quotes…), stops the server at startup
32
+ - `--static-csp` (`DM_STATIC_CSP`): the `Content-Security-Policy` of the
33
+ files it serves, default `frame-ancestors 'self'`; `off` sends none. See
34
+ [Security headers](#security-headers)
35
+ - `--static-frame-options` (`DM_STATIC_FRAME_OPTIONS`): the
36
+ `X-Frame-Options` of the files it serves, default `SAMEORIGIN`; `off`
37
+ sends none
28
38
 
29
39
  ## How It Works
30
40
 
@@ -105,7 +115,7 @@ Configuration keys are transformed:
105
115
  | `__LDAP_BASE__` | `--ldap-base` | `dc=example,dc=com` |
106
116
  | `__LDAP_TOP_ORGANIZATION__` | `--ldap-top-organization` | `ou=organization,dc=example,dc=com` |
107
117
  | `__API_PREFIX__` | `--api-prefix` | `/api` |
108
- | `__STATIC_PREFIX__` | `--static-prefix` | `/static` |
118
+ | `__STATIC_NAME__` | `--static-name` | `static` |
109
119
 
110
120
  ## Use Cases
111
121
 
@@ -137,17 +147,17 @@ Serve web-based LDAP management interface:
137
147
  ```bash
138
148
  --plugin core/static \
139
149
  --static-path ./static \
140
- --static-prefix /
150
+ --static-name app
141
151
  ```
142
152
 
143
153
  Files:
144
154
 
145
155
  ```
146
156
  ./static/
147
- index.html → http://localhost:8081/
148
- css/app.css → http://localhost:8081/css/app.css
149
- js/ldap-tree.js → http://localhost:8081/js/ldap-tree.js
150
- schemas/users.json → http://localhost:8081/schemas/users.json
157
+ index.html → http://localhost:8081/app/
158
+ css/app.css → http://localhost:8081/app/css/app.css
159
+ js/ldap-tree.js → http://localhost:8081/app/js/ldap-tree.js
160
+ schemas/users.json → http://localhost:8081/app/schemas/users.json
151
161
  ```
152
162
 
153
163
  ### 3. API Documentation
@@ -157,7 +167,7 @@ Serve OpenAPI/Swagger documentation:
157
167
  ```bash
158
168
  --plugin core/static \
159
169
  --static-path ./api-docs \
160
- --static-prefix /docs
170
+ --static-name docs
161
171
  ```
162
172
 
163
173
  ```
@@ -172,7 +182,7 @@ GET /docs/swagger-ui/ → ./api-docs/swagger-ui/index.html
172
182
  ```bash
173
183
  --plugin core/static \
174
184
  --static-path ./public \
175
- --static-prefix /static
185
+ --static-name static
176
186
  ```
177
187
 
178
188
  **Directory structure:**
@@ -198,25 +208,17 @@ http://localhost:8081/static/js/app.js
198
208
 
199
209
  ### Example 2: Root Path Serving
200
210
 
201
- ```bash
202
- --plugin core/static \
203
- --static-path ./web \
204
- --static-prefix /
205
- ```
206
-
207
- Serves files from root:
208
-
209
- ```
210
- http://localhost:8081/index.html
211
- http://localhost:8081/about.html
212
- ```
211
+ Not supported: the files are always served under `/<static-name>/`, and
212
+ the schema routes, which replace the placeholders, are built on that name.
213
+ To show an application at `/`, have the reverse proxy redirect `/` to it,
214
+ for instance to `/static/index.html`.
213
215
 
214
216
  ### Example 3: Schema Deployment
215
217
 
216
218
  ```bash
217
219
  --plugin core/static \
218
220
  --static-path ./static \
219
- --static-prefix /static \
221
+ --static-name static \
220
222
  --ldap-base "dc=example,dc=com"
221
223
  ```
222
224
 
@@ -298,6 +300,43 @@ For restricted access:
298
300
  2. Implement custom middleware
299
301
  3. Use reverse proxy with access controls
300
302
 
303
+ ### Security headers
304
+
305
+ The files served here are applications, an administration console among
306
+ them. So that no other origin frames one signed in (clickjacking), another
307
+ sub-domain or port of the same site included, the responses under the prefix
308
+ carry:
309
+
310
+ ```
311
+ Content-Security-Policy: frame-ancestors 'self'
312
+ X-Frame-Options: SAMEORIGIN
313
+ ```
314
+
315
+ `X-Frame-Options` is for browsers that ignore `frame-ancestors`. A page that
316
+ must be framed by another origin names it:
317
+
318
+ ```bash
319
+ --static-csp "frame-ancestors 'self' https://portal.example.com"
320
+ --static-frame-options off
321
+ ```
322
+
323
+ A full policy can be given the same way, for instance
324
+ `default-src 'self'; frame-ancestors 'none'`, as long as the applications
325
+ served allow it: the policy applies to every file under the prefix. One
326
+ written on several lines, as a YAML block holds it, is sent as one line; a
327
+ value a header cannot hold stops the server at startup. An empty value keeps
328
+ the default, as a compose file expands an unset `${VAR:-}` to one; `off`
329
+ sends no header (`false` in a plugin's JSON override), when the reverse
330
+ proxy sets its own. Keeping both is harmless as long as they agree.
331
+
332
+ A redirect or a 404 that Express answers itself (a directory without its
333
+ slash, a missing file) carries `Content-Security-Policy: default-src 'none'`
334
+ instead; `X-Frame-Options`, unless set to `off`, still keeps it from being
335
+ framed, and it shows nothing to click.
336
+
337
+ Every response of the server, API routes included, also carries
338
+ `X-Content-Type-Options: nosniff`, and none says `X-Powered-By: Express`.
339
+
301
340
  ## MIME Types
302
341
 
303
342
  Express automatically sets Content-Type based on file extension:
@@ -374,9 +413,9 @@ GET /static/index.html → 404
374
413
  --static-path ./static
375
414
  ```
376
415
 
377
- 3. Verify prefix matches URL:
416
+ 3. Verify that the name matches the first segment of the URL:
378
417
  ```bash
379
- --static-prefix /static
418
+ --static-name static
380
419
  ```
381
420
 
382
421
  ### Problem: Placeholders Not Replaced
@@ -4,6 +4,32 @@ What to check before deploying, newest first. Only releases that need a
4
4
  decision or a configuration change appear here; see the
5
5
  [CHANGELOG](../../CHANGELOG.md) for everything else.
6
6
 
7
+ ## From 1.0 to 1.1.0
8
+
9
+ ### The static files can no longer be framed by another origin
10
+
11
+ `core/static` sends `Content-Security-Policy: frame-ancestors 'self'` and
12
+ `X-Frame-Options: SAMEORIGIN`. A page of another origin, another sub-domain
13
+ or port of the same site included, that showed one of its applications in an
14
+ `<iframe>`, a portal for instance, now shows an empty frame. Name it:
15
+
16
+ ```bash
17
+ --static-csp "frame-ancestors 'self' https://portal.example.com"
18
+ --static-frame-options off
19
+ ```
20
+
21
+ See [security headers](plugins/utilities/static.md#security-headers).
22
+
23
+ ### `--static-name`: an empty value is `static`
24
+
25
+ With `DM_STATIC_NAME=` the files were served at the root while the
26
+ configuration API and SCIM announced `/static`; they are now served under
27
+ `/static`. Slashes around the name are left out (`/static/` is `static`). A
28
+ name Express would not match as written, with an empty, `.` or `..` segment,
29
+ or what a URL encodes or Express reads as route syntax in it (a space, a
30
+ letter outside ASCII, `:`, `*`, `?`, `#`, `%`, brackets, quotes…), stops
31
+ the server at startup.
32
+
7
33
  ## From 0.26 to 1.0.0
8
34
 
9
35
  Every breaking change since 0.26.0. Authorization fails closed in 1.0.0:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-rest",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Light directory manager",
5
5
  "keywords": [
6
6
  "Identity-Manager"