@tachybase/plugin-password-policy 1.2.10 → 1.2.11
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/client/index.js +2 -2
- package/dist/client/password-validity-period/ViewPasswordValidityPeriod.d.ts +1 -0
- package/dist/client/password-validity-period/ViewPasswordValidityPeriod.schema.d.ts +42 -0
- package/dist/client/password-validity-period/usePasswordPolicyValues.d.ts +3 -0
- package/dist/client/password-validity-period/useSavePasswordPolicyValues.d.ts +3 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +3 -0
- package/dist/externalVersion.js +6 -6
- package/dist/locale/en-US.json +4 -0
- package/dist/locale/zh-CN.json +4 -0
- package/dist/node_modules/geoip-lite/package.json +1 -1
- package/dist/server/actions/PasswordPolicyController.d.ts +7 -0
- package/dist/server/actions/PasswordPolicyController.js +124 -0
- package/dist/server/collections/passwordPolicy.d.ts +2 -0
- package/dist/server/collections/passwordPolicy.js +41 -0
- package/dist/server/plugin.js +9 -2
- package/dist/server/services/PasswordPolicyService.d.ts +75 -0
- package/dist/server/services/PasswordPolicyService.js +595 -0
- package/package.json +7 -7
package/dist/client/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(s,t){typeof exports=="object"&&typeof module!="undefined"?t(exports,require("@tachybase/client"),require("react/jsx-runtime"),require("@tachybase/schema"),require("antd")):typeof define=="function"&&define.amd?define(["exports","@tachybase/client","react/jsx-runtime","@tachybase/schema","antd"],t):(s=typeof globalThis!="undefined"?globalThis:s||self,t(s["@tachybase/plugin-password-policy"]={},s["@tachybase/client"],s.jsxRuntime,s["@tachybase/schema"],s.antd))})(this,function(s,t,n,l,
|
|
1
|
+
(function(s,t){typeof exports=="object"&&typeof module!="undefined"?t(exports,require("@tachybase/client"),require("react/jsx-runtime"),require("@tachybase/schema"),require("antd")):typeof define=="function"&&define.amd?define(["exports","@tachybase/client","react/jsx-runtime","@tachybase/schema","antd"],t):(s=typeof globalThis!="undefined"?globalThis:s||self,t(s["@tachybase/plugin-password-policy"]={},s["@tachybase/client"],s.jsxRuntime,s["@tachybase/schema"],s.antd))})(this,function(s,t,n,l,d){"use strict";var W=Object.defineProperty;var g=Object.getOwnPropertySymbols;var G=Object.prototype.hasOwnProperty,H=Object.prototype.propertyIsEnumerable;var f=(s,t,n)=>t in s?W(s,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):s[t]=n,h=(s,t)=>{for(var n in t||(t={}))G.call(t,n)&&f(s,n,t[n]);if(g)for(var n of g(t))H.call(t,n)&&f(s,n,t[n]);return s};var y=(s,t,n)=>new Promise((l,d)=>{var b=p=>{try{x(n.next(p))}catch(e){d(e)}},m=p=>{try{x(n.throw(p))}catch(e){d(e)}},x=p=>p.done?l(p.value):Promise.resolve(p.value).then(b,m);x((n=n.apply(s,t)).next())});const b="password-policy";var m=(o=>(o[o.None=0]="None",o[o.NumberAndLetter=1]="NumberAndLetter",o[o.NumberAndLetterAndSymbol=2]="NumberAndLetterAndSymbol",o[o.NumberAndLetterAndUpperAndLower=3]="NumberAndLetterAndUpperAndLower",o[o.NumberAndLetterAndUpperAndLowerAndSymbol=4]="NumberAndLetterAndUpperAndLowerAndSymbol",o[o.NumberAndLetterAndUpperAndLowerAndSymbol3=5]="NumberAndLetterAndUpperAndLowerAndSymbol3",o))(m||{});const x="passwordPolicy";function p(){return{t:(r,a={})=>t.i18n.t(r,h({ns:b},a))}}const e=o=>t.tval(o,{ns:b}),v=()=>{const o=t.useAPIClient(),{data:r}=t.useRequest(()=>o.resource("ipFilter").get().then(i=>{var c;return(c=i.data)==null?void 0:c.data}));return{form:l.createForm({values:r})}},w=()=>{const o=l.useForm(),{message:r}=d.App.useApp(),a=t.useAPIClient(),{t:i}=p();return{run(){return y(this,null,function*(){yield o.submit();try{yield a.request({url:"ipFilter:put",method:"post",data:o.values}),r.success(i("Settings saved successfully"))}catch(u){throw r.error(i("Failed to save settings")),u}})}}},P={type:"object",properties:{ipFilter:{"x-component":"FormV2","x-use-component-props":"useIPFilterValues",type:"void",title:e("IP Filter Settings"),properties:{allowFirst:{type:"boolean",title:e("Filter Mode"),default:!0,description:e("Choose how to apply white and black lists"),"x-decorator":"FormItem","x-component":"Radio.Group",enum:[{label:e("Allow first (whitelist priority)"),value:!0},{label:e("Block first (blacklist priority)"),value:!1}]},allowList:{type:"string",title:e("Allow List (Whitelist)"),"x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{rows:6,placeholder:`127.0.0.1
|
|
2
2
|
192.168.0.0/16
|
|
3
3
|
10.0.0.0/8`},description:e("One IP or CIDR per line. Example: 192.168.1.0/24 or 10.0.0.1")},blockList:{type:"string",title:e("Block List (Blacklist)"),"x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{rows:6,placeholder:`1.2.3.4
|
|
4
|
-
5.6.0.0/16`},description:e("One IP or CIDR per line. Example: 1.2.3.4 or 5.6.7.0/24")},footer:{type:"void","x-component":"ActionBar",properties:{submit:{title:e("Save"),"x-component":"Action","x-component-props":{type:"primary",useAction:"{{ useSaveIPFilterValues }}"}}}}}}}},w=()=>n.jsx(a.Card,{bordered:!1,children:n.jsx(t.SchemaComponent,{schema:k,scope:{useIPFilterValues:u,useSaveIPFilterValues:v}})}),C=()=>{const o=t.useAPIClient(),{data:r}=t.useRequest(()=>o.resource("passwordAttempt").get().then(i=>{var m;return(m=i.data)==null?void 0:m.data}));return{form:l.createForm({values:r})}},S=()=>{const o=l.useForm(),{message:r}=a.App.useApp(),p=t.useAPIClient(),{t:i}=d();return{run(){return y(this,null,function*(){yield o.submit();try{yield p.request({url:"passwordAttempt:put",method:"post",data:o.values}),r.success(i("Saved successfully"))}catch(x){throw r.error(i("Failed to save settings")),x}})}}},F={type:"object",properties:{passwordAttempt:{"x-component":"FormV2","x-use-component-props":"usePasswordPolicyValues",type:"void",title:e("Password policy"),properties:{maxAttempts:{type:"digit",title:e("Max invalid password sign-in attempts"),description:e("Default value is 5. Set to 0 to disable."),"x-decorator":"FormItem","x-component":"InputNumber","x-validator":{required:!0},"x-component-props":{min:0}},windowSeconds:{type:"digit",title:e("Max invalid password sign-in attempts interval (seconds)"),description:e("Default value is 300 seconds (5 minutes)."),"x-decorator":"FormItem","x-component":"InputNumber","x-validator":{required:!0},"x-component-props":{min:1}},lockSeconds:{type:"digit",title:e("Lockout duration (seconds)"),description:e("Default value is 1800 seconds (30 minutes)."),"x-decorator":"FormItem","x-component":"InputNumber","x-validator":{required:!0},"x-component-props":{min:1}},strictLock:{type:"boolean",title:e("Can not access any api after locked"),description:e("Default value is off."),"x-decorator":"FormItem","x-component":"Checkbox"},footer:{type:"void","x-component":"ActionBar",properties:{submit:{title:'{{t("Submit")}}',"x-component":"Action","x-component-props":{type:"primary",useAction:"{{ useSavePasswordPolicyValues }}"}}}}}}}},P=()=>n.jsx(a.Card,{bordered:!1,children:n.jsx(t.SchemaComponent,{schema:F,scope:{usePasswordPolicyValues:C,useSavePasswordPolicyValues:S}})}),I=()=>{const o=t.useAPIClient(),{data:r}=t.useRequest(()=>o.resource("passwordStrengthConfig").get().then(i=>{var m;return(m=i.data)==null?void 0:m.data}));return{form:l.createForm({values:r})}},L=()=>{const o=l.useForm(),{message:r}=a.App.useApp(),p=t.useAPIClient(),{t:i}=d();return{run(){return y(this,null,function*(){yield o.submit();try{yield p.request({url:"passwordStrengthConfig:put",method:"post",data:o.values}),r.success(i("Saved successfully"))}catch(x){throw r.error(i("Failed to save settings")),x}})}}},T={type:"object",properties:{passwordStrength:{"x-component":"FormV2","x-use-component-props":"usePasswordStrengthValues",type:"void",title:e("Password Strength Settings"),properties:{minLength:{type:"number",title:e("Minimum Password Length"),"x-decorator":"FormItem","x-component":"InputNumber",default:0},strengthLevel:{type:"number",title:e("Password Strength Requirements"),"x-decorator":"FormItem","x-component":"Select",enum:[{label:e("No restrictions"),value:c.None},{label:e("Must contain letters and numbers"),value:c.NumberAndLetter},{label:e("Must contain letters, numbers, and symbols"),value:c.NumberAndLetterAndSymbol},{label:e("Must contain numbers, uppercase and lowercase letters"),value:c.NumberAndLetterAndUpperAndLower},{label:e("Must contain numbers, uppercase and lowercase letters, and symbols"),value:c.NumberAndLetterAndUpperAndLowerAndSymbol},{label:e("Must contain at least 3 of the following: numbers, uppercase letters, lowercase letters, and symbols"),value:c.NumberAndLetterAndUpperAndLowerAndSymbol3}],default:0},notContainUsername:{type:"boolean",title:e("Password cannot contain username"),"x-decorator":"FormItem","x-component":"Checkbox",default:!1},historyCount:{type:"number",title:e("Remember password history (0-24)"),description:e("Number of previous passwords to remember. 0 means no restriction."),"x-decorator":"FormItem","x-component":"InputNumber","x-component-props":{min:0,max:24},default:0},footer:{type:"void","x-component":"ActionBar",properties:{submit:{title:'{{t("Submit")}}',"x-component":"Action","x-component-props":{type:"primary",useAction:"{{ useSavePasswordStrengthValues }}"}}}}}}}},V=()=>n.jsx(a.Card,{bordered:!1,children:n.jsx(t.SchemaComponent,{schema:T,scope:{usePasswordStrengthValues:I,useSavePasswordStrengthValues:L}})}),N={name:"signInFails",fields:[{type:"string",name:"username",interface:"input",uiSchema:{type:"string",title:'{{ t("Username") }}',"x-read-pretty":!0,"x-component"(){var r;return(r=t.useRecord().user)==null?void 0:r.username}}},{type:"string",name:"nickname",interface:"input",uiSchema:{type:"string",title:'{{ t("Nickname") }}',"x-read-pretty":!0,"x-component"(){var r;return(r=t.useRecord().user)==null?void 0:r.nickname}}},{type:"string",name:"ip",interface:"input",uiSchema:{type:"string",title:"IP","x-read-pretty":!0}},{type:"string",name:"address",interface:"input",uiSchema:{type:"string",title:e("Location"),"x-read-pretty":!0}},{type:"datetime",name:"createdAt",interface:"datetime",uiSchema:{type:"string",title:e("Time"),"x-component":"DatePicker","x-component-props":{showTime:!0},"x-read-pretty":!0}},{name:"user",type:"belongsTo",target:"users",targetKey:"id",foreignKey:"userId",interface:"m2o",uiSchema:{type:"object",title:'{{t("User")}}',"x-component":"AssociationField","x-component-props":{fieldNames:{value:"id",label:"nickname"}},"x-read-pretty":!0}}]},D={type:"void",properties:{signInFails:{type:"void","x-decorator":"TableBlockProvider","x-acl-action":"signInFails:list","x-use-decorator-props":"useTableBlockDecoratorProps","x-decorator-props":{dataSource:"main",collection:"signInFails",action:"list",params:{pageSize:20,appends:["user"]},rowKey:"id",showIndex:!0,dragSort:!1},"x-component":"CardItem",properties:{actions:{type:"void","x-component":"ActionBar","x-component-props":{style:{marginBottom:"var(--tb-spacing)"}},properties:{filter:{type:"void",title:'{{ t("Filter") }}',"x-action":"filter","x-component":"Filter.Action","x-use-component-props":"useFilterActionProps","x-component-props":{icon:"FilterOutlined"},"x-align":"left"},refresh:{type:"void",title:'{{ t("Refresh") }}',"x-action":"refresh","x-component":"Action","x-settings":"actionSettings:refresh","x-component-props":{icon:"ReloadOutlined"},"x-use-component-props":"useRefreshActionProps","x-align":"right"}}},table:{type:"array","x-component":"TableV2","x-use-component-props":"useTableBlockProps","x-component-props":{rowKey:"id",rowSelection:{type:"checkbox"}},properties:{username:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{username:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},nickname:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{nickname:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},ip:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"CollectionField",properties:{ip:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},address:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"CollectionField",properties:{address:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},createdAt:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"CollectionField",properties:{createdAt:{type:"datetime","x-component":"CollectionField","x-read-pretty":!0}}}}}}}}},B=()=>n.jsx(t.ExtendCollectionsProvider,{collections:[N],children:n.jsx(t.SchemaComponent,{schema:D})}),U={name:"userLocks",fields:[{type:"string",name:"username",interface:"input",uiSchema:{type:"string",title:'{{ t("Username") }}',"x-read-pretty":!0,"x-component"(){var r;return(r=t.useRecord().user)==null?void 0:r.username}}},{type:"string",name:"nickname",interface:"input",uiSchema:{type:"string",title:'{{ t("Nickname") }}',"x-read-pretty":!0,"x-component"(){var r;return(r=t.useRecord().user)==null?void 0:r.nickname}}},{type:"datetime",name:"updatedAt",interface:"datetime",uiSchema:{type:"string",title:e("Updated At"),"x-component":"DatePicker","x-component-props":{showTime:!0},"x-read-pretty":!0}},{type:"datetime",name:"expireAt",interface:"datetime",uiSchema:{type:"string",title:e("Lock Expires At"),"x-component":"DatePicker","x-component-props":{showTime:!0},"x-read-pretty":!0}},{name:"user",type:"belongsTo",target:"users",targetKey:"id",foreignKey:"userId",interface:"m2o",uiSchema:{type:"object",title:'{{t("User")}}',"x-component":"AssociationField","x-component-props":{fieldNames:{value:"id",label:"nickname"}},"x-read-pretty":!0}}]},M={type:"void","x-acl-action-props":{skipScopeCheck:!0},"x-acl-action":"userLocks:create","x-decorator":"FormBlockProvider","x-use-decorator-props":"useCreateFormBlockDecoratorProps","x-decorator-props":{dataSource:"main",collection:"userLocks"},"x-component":"CardItem",properties:{form:{type:"void","x-component":"FormV2","x-use-component-props":"useCreateFormBlockProps",properties:{actionBar:{type:"void","x-component":"ActionBar","x-component-props":{style:{marginBottom:"var(--tb-spacing)"}},properties:{submit:{title:'{{t("Submit")}}',"x-action":"submit","x-component":"Action","x-use-component-props":"useCreateActionProps","x-component-props":{type:"primary",htmlType:"submit"},type:"void"}}},userId:{type:"string",title:e("Username or Email"),required:!0,"x-component":"RemoteSelect","x-decorator":"FormItem","x-component-props":{placeholder:e("Please enter username, nickname or email"),fieldNames:{label:"nickname",value:"id"},service:{resource:"users",action:"list",params:{filter:{$or:[{lock:{id:{$notExists:!0}}},{lock:{expireAt:{$lt:"{{ new Date() }}"}}}]},pageSize:20,sort:["-id"],appends:["lock"]}},manual:!1}},expireAt:{type:"string",title:e("Lock Until"),required:!0,"x-component":"DatePicker","x-decorator":"FormItem","x-component-props":{showTime:!0,disabledDate:"{{ (current) => current && current < new Date() }}",placeholder:e("Please select lock expiration time")}}}}}},q={type:"void","x-action":"create","x-acl-action":"create",title:e("Lock New User"),"x-component":"Action","x-decorator":"ACLActionProvider","x-component-props":{openMode:"drawer",type:"primary",icon:"PlusOutlined"},"x-align":"right","x-acl-action-props":{skipScopeCheck:!0},properties:{drawer:{type:"void",title:e("Lock New User"),"x-component":"Action.Container","x-component-props":{className:"tb-action-popup"},properties:{form:M}}}},E={type:"void",title:'{{ t("Edit") }}',"x-action":"update","x-component":"Action.Link","x-component-props":{openMode:"drawer",icon:"EditOutlined"},"x-decorator":"ACLActionProvider",properties:{drawer:{type:"void",title:'{{ t("Edit") }}',"x-component":"Action.Container","x-component-props":{className:"tb-action-popup"},properties:{form:{type:"void","x-acl-action-props":{skipScopeCheck:!1},"x-decorator":"FormBlockProvider","x-use-decorator-props":"useEditFormBlockDecoratorProps","x-decorator-props":{dataSource:"main",collection:"userLocks",action:"get",useParams:"{{ useParamsFromRecord }}",params:{appends:["user"]}},"x-component":"CardItem",properties:{form:{type:"void","x-component":"FormV2","x-use-component-props":"useEditFormBlockProps",properties:{actionBar:{type:"void","x-component":"ActionBar","x-component-props":{style:{marginBottom:"var(--tb-spacing)"}},properties:{submit:{title:'{{t("Submit")}}',"x-action":"submit","x-component":"Action","x-use-component-props":"useUpdateActionProps","x-component-props":{type:"primary",htmlType:"submit"},"x-action-settings":{triggerWorkflows:[],onSuccess:{manualClose:!1,redirecting:!1,successMessage:'{{t("Updated successfully")}}'},skipValidator:!1},type:"void"}}},expireAt:{type:"date",title:e("Lock Until"),required:!0,"x-component":"DatePicker","x-component-props":{showTime:!0,disabledDate:"{{ (current) => current && current < new Date() }}",placeholder:e("Please select lock expiration time")},"x-decorator":"FormItem"}}}}}}}}},O={type:"void",properties:{userLocks:{type:"void","x-decorator":"TableBlockProvider","x-acl-action":"userLocks:list","x-use-decorator-props":"useTableBlockDecoratorProps","x-decorator-props":{dataSource:"main",collection:"userLocks",action:"list",params:{pageSize:20,appends:["user"]},rowKey:"id",showIndex:!0,dragSort:!1},"x-component":"CardItem",properties:{actions:{type:"void","x-component":"ActionBar","x-component-props":{style:{marginBottom:"var(--tb-spacing)"}},properties:{filter:{type:"void",title:'{{ t("Filter") }}',"x-action":"filter","x-component":"Filter.Action","x-use-component-props":"useFilterActionProps","x-component-props":{icon:"FilterOutlined"},"x-align":"left"},refresh:{type:"void",title:'{{ t("Refresh") }}',"x-action":"refresh","x-component":"Action","x-settings":"actionSettings:refresh","x-component-props":{icon:"ReloadOutlined"},"x-use-component-props":"useRefreshActionProps","x-align":"right"},create:q}},table:{type:"array","x-component":"TableV2","x-use-component-props":"useTableBlockProps","x-component-props":{rowKey:"id",rowSelection:{type:"checkbox"}},properties:{username:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{username:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},nickname:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{nickname:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},updatedAt:{type:"void",title:e("Updated At"),"x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{updatedAt:{"x-collection-field":"userLocks.updatedAt","x-component":"CollectionField","x-component-props":{ellipsis:!0,showTime:!0},"x-read-pretty":!0,"x-decorator":null}}},expireAt:{type:"void",title:e("Lock Expires At"),"x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{expireAt:{"x-collection-field":"userLocks.expireAt","x-component":"CollectionField","x-component-props":{ellipsis:!0,showTime:!0},"x-read-pretty":!0,"x-decorator":null}}},actions:{type:"void",title:'{{ t("Actions") }}',"x-action-column":"actions","x-decorator":"TableV2.Column.ActionBar","x-component":"TableV2.Column","x-component-props":{width:200,fixed:"right"},properties:{actions:{type:"void","x-component":"Space","x-component-props":{split:"|"},properties:{edit:E,destroy:{type:"void",title:e("Unlock"),"x-action":"destroy","x-component":"Action.Link","x-component-props":{danger:!0,confirm:{title:e("Unlock User"),content:e("Are you sure you want to unlock this user?")}},"x-decorator":"ACLActionProvider","x-use-component-props":"useDestroyActionProps"}}}}}}}}}}},R=()=>n.jsx(t.ExtendCollectionsProvider,{collections:[U],children:n.jsx(t.SchemaComponent,{schema:O})});class A extends t.Plugin{load(){return y(this,null,function*(){this.app.systemSettingsManager.add("security.password-attempt",{icon:"SettingOutlined",title:e("Password policy"),Component:P,aclSnippet:"pm.security.password-attempt"}),this.app.systemSettingsManager.add("security.user-lock",{icon:"UserOutlined",title:e("User lock"),Component:R,aclSnippet:"pm.security.user-lock"}),this.app.systemSettingsManager.add("security.ip-filter",{icon:"GlobalOutlined",title:e("IP policy"),Component:w,aclSnippet:"pm.security.ip-filter"}),this.app.systemSettingsManager.add("security.password-strength",{icon:"LockOutlined",title:e("Password strength"),Component:V,aclSnippet:"pm.security.password-strength"}),this.app.systemSettingsManager.add("security.sign-in-fails",{icon:"HistoryOutlined",title:e("Sign-in fails history"),Component:B,aclSnippet:"pm.security.sign-in-fails"})})}}s.PasswordPolicyClient=A,s.default=A,Object.defineProperties(s,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
|
4
|
+
5.6.0.0/16`},description:e("One IP or CIDR per line. Example: 1.2.3.4 or 5.6.7.0/24")},footer:{type:"void","x-component":"ActionBar",properties:{submit:{title:e("Save"),"x-component":"Action","x-component-props":{type:"primary",useAction:"{{ useSaveIPFilterValues }}"}}}}}}}},S=()=>n.jsx(d.Card,{bordered:!1,children:n.jsx(t.SchemaComponent,{schema:P,scope:{useIPFilterValues:v,useSaveIPFilterValues:w}})}),C=()=>{const o=t.useAPIClient(),{data:r}=t.useRequest(()=>o.resource(x).get().then(i=>{var c;return(c=i.data)==null?void 0:c.data}));return{form:l.createForm({values:r})}},k=()=>{const o=l.useForm(),{message:r}=d.App.useApp(),a=t.useAPIClient(),{t:i}=t.useTranslation();return{run(){return y(this,null,function*(){yield o.submit();try{yield a.request({url:`${x}:set`,method:"post",data:o.values}),r.success(i("Saved successfully"))}catch(u){throw r.error(i("Failed to save settings")),u}})}}},F=()=>({type:"object",properties:{passwordValidityPeriod:{type:"void",title:e("Password validity period"),"x-decorator":"CardItem","x-component":"FormV2","x-use-component-props":"usePasswordPolicyValues",properties:{validityPeriod:{type:"number",title:e("Password validity period (days)"),description:e("Default value is never."),"x-decorator":"FormItem","x-component":"Select","x-component-props":{defaultValue:0,options:[{label:"30",value:30},{label:"60",value:60},{label:"90",value:90},{label:"180",value:180},{label:"365",value:365},{label:e("Never expire"),value:0}]}},footer:{type:"void","x-component":"ActionBar",properties:{submit:{title:'{{t("Submit")}}',"x-component":"Action","x-component-props":{type:"primary",useAction:"{{ useSavePasswordPolicyValues }}"}}}}}}}}),I=()=>{const o=F();return n.jsx(t.SchemaComponent,{schema:o,scope:{usePasswordPolicyValues:C,useSavePasswordPolicyValues:k}})},L=()=>{const o=t.useAPIClient(),{data:r}=t.useRequest(()=>o.resource("passwordAttempt").get().then(i=>{var c;return(c=i.data)==null?void 0:c.data}));return{form:l.createForm({values:r})}},V=()=>{const o=l.useForm(),{message:r}=d.App.useApp(),a=t.useAPIClient(),{t:i}=p();return{run(){return y(this,null,function*(){yield o.submit();try{yield a.request({url:"passwordAttempt:put",method:"post",data:o.values}),r.success(i("Saved successfully"))}catch(u){throw r.error(i("Failed to save settings")),u}})}}},T={type:"object",properties:{passwordAttempt:{"x-component":"FormV2","x-use-component-props":"usePasswordPolicyValues",type:"void",title:e("Password policy"),properties:{maxAttempts:{type:"digit",title:e("Max invalid password sign-in attempts"),description:e("Default value is 5. Set to 0 to disable."),"x-decorator":"FormItem","x-component":"InputNumber","x-validator":{required:!0},"x-component-props":{min:0}},windowSeconds:{type:"digit",title:e("Max invalid password sign-in attempts interval (seconds)"),description:e("Default value is 300 seconds (5 minutes)."),"x-decorator":"FormItem","x-component":"InputNumber","x-validator":{required:!0},"x-component-props":{min:1}},lockSeconds:{type:"digit",title:e("Lockout duration (seconds)"),description:e("Default value is 1800 seconds (30 minutes)."),"x-decorator":"FormItem","x-component":"InputNumber","x-validator":{required:!0},"x-component-props":{min:1}},strictLock:{type:"boolean",title:e("Can not access any api after locked"),description:e("Default value is off."),"x-decorator":"FormItem","x-component":"Checkbox"},footer:{type:"void","x-component":"ActionBar",properties:{submit:{title:'{{t("Submit")}}',"x-component":"Action","x-component-props":{type:"primary",useAction:"{{ useSavePasswordPolicyValues }}"}}}}}}}},N=()=>n.jsx(d.Card,{bordered:!1,children:n.jsx(t.SchemaComponent,{schema:T,scope:{usePasswordPolicyValues:L,useSavePasswordPolicyValues:V}})}),D=()=>{const o=t.useAPIClient(),{data:r}=t.useRequest(()=>o.resource("passwordStrengthConfig").get().then(i=>{var c;return(c=i.data)==null?void 0:c.data}));return{form:l.createForm({values:r})}},B=()=>{const o=l.useForm(),{message:r}=d.App.useApp(),a=t.useAPIClient(),{t:i}=p();return{run(){return y(this,null,function*(){yield o.submit();try{yield a.request({url:"passwordStrengthConfig:put",method:"post",data:o.values}),r.success(i("Saved successfully"))}catch(u){throw r.error(i("Failed to save settings")),u}})}}},U={type:"object",properties:{passwordStrength:{"x-component":"FormV2","x-use-component-props":"usePasswordStrengthValues",type:"void",title:e("Password Strength Settings"),properties:{minLength:{type:"number",title:e("Minimum Password Length"),"x-decorator":"FormItem","x-component":"InputNumber",default:0},strengthLevel:{type:"number",title:e("Password Strength Requirements"),"x-decorator":"FormItem","x-component":"Select",enum:[{label:e("No restrictions"),value:m.None},{label:e("Must contain letters and numbers"),value:m.NumberAndLetter},{label:e("Must contain letters, numbers, and symbols"),value:m.NumberAndLetterAndSymbol},{label:e("Must contain numbers, uppercase and lowercase letters"),value:m.NumberAndLetterAndUpperAndLower},{label:e("Must contain numbers, uppercase and lowercase letters, and symbols"),value:m.NumberAndLetterAndUpperAndLowerAndSymbol},{label:e("Must contain at least 3 of the following: numbers, uppercase letters, lowercase letters, and symbols"),value:m.NumberAndLetterAndUpperAndLowerAndSymbol3}],default:0},notContainUsername:{type:"boolean",title:e("Password cannot contain username"),"x-decorator":"FormItem","x-component":"Checkbox",default:!1},historyCount:{type:"number",title:e("Remember password history (0-24)"),description:e("Number of previous passwords to remember. 0 means no restriction."),"x-decorator":"FormItem","x-component":"InputNumber","x-component-props":{min:0,max:24},default:0},footer:{type:"void","x-component":"ActionBar",properties:{submit:{title:'{{t("Submit")}}',"x-component":"Action","x-component-props":{type:"primary",useAction:"{{ useSavePasswordStrengthValues }}"}}}}}}}},M=()=>n.jsx(d.Card,{bordered:!1,children:n.jsx(t.SchemaComponent,{schema:U,scope:{usePasswordStrengthValues:D,useSavePasswordStrengthValues:B}})}),q={name:"signInFails",fields:[{type:"string",name:"username",interface:"input",uiSchema:{type:"string",title:'{{ t("Username") }}',"x-read-pretty":!0,"x-component"(){var r;return(r=t.useRecord().user)==null?void 0:r.username}}},{type:"string",name:"nickname",interface:"input",uiSchema:{type:"string",title:'{{ t("Nickname") }}',"x-read-pretty":!0,"x-component"(){var r;return(r=t.useRecord().user)==null?void 0:r.nickname}}},{type:"string",name:"ip",interface:"input",uiSchema:{type:"string",title:"IP","x-read-pretty":!0}},{type:"string",name:"address",interface:"input",uiSchema:{type:"string",title:e("Location"),"x-read-pretty":!0}},{type:"datetime",name:"createdAt",interface:"datetime",uiSchema:{type:"string",title:e("Time"),"x-component":"DatePicker","x-component-props":{showTime:!0},"x-read-pretty":!0}},{name:"user",type:"belongsTo",target:"users",targetKey:"id",foreignKey:"userId",interface:"m2o",uiSchema:{type:"object",title:'{{t("User")}}',"x-component":"AssociationField","x-component-props":{fieldNames:{value:"id",label:"nickname"}},"x-read-pretty":!0}}]},O={type:"void",properties:{signInFails:{type:"void","x-decorator":"TableBlockProvider","x-acl-action":"signInFails:list","x-use-decorator-props":"useTableBlockDecoratorProps","x-decorator-props":{dataSource:"main",collection:"signInFails",action:"list",params:{pageSize:20,appends:["user"]},rowKey:"id",showIndex:!0,dragSort:!1},"x-component":"CardItem",properties:{actions:{type:"void","x-component":"ActionBar","x-component-props":{style:{marginBottom:"var(--tb-spacing)"}},properties:{filter:{type:"void",title:'{{ t("Filter") }}',"x-action":"filter","x-component":"Filter.Action","x-use-component-props":"useFilterActionProps","x-component-props":{icon:"FilterOutlined"},"x-align":"left"},refresh:{type:"void",title:'{{ t("Refresh") }}',"x-action":"refresh","x-component":"Action","x-settings":"actionSettings:refresh","x-component-props":{icon:"ReloadOutlined"},"x-use-component-props":"useRefreshActionProps","x-align":"right"}}},table:{type:"array","x-component":"TableV2","x-use-component-props":"useTableBlockProps","x-component-props":{rowKey:"id",rowSelection:{type:"checkbox"}},properties:{username:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{username:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},nickname:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{nickname:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},ip:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"CollectionField",properties:{ip:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},address:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"CollectionField",properties:{address:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},createdAt:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"CollectionField",properties:{createdAt:{type:"datetime","x-component":"CollectionField","x-read-pretty":!0}}}}}}}}},E=()=>n.jsx(t.ExtendCollectionsProvider,{collections:[q],children:n.jsx(t.SchemaComponent,{schema:O})}),R={name:"userLocks",fields:[{type:"string",name:"username",interface:"input",uiSchema:{type:"string",title:'{{ t("Username") }}',"x-read-pretty":!0,"x-component"(){var r;return(r=t.useRecord().user)==null?void 0:r.username}}},{type:"string",name:"nickname",interface:"input",uiSchema:{type:"string",title:'{{ t("Nickname") }}',"x-read-pretty":!0,"x-component"(){var r;return(r=t.useRecord().user)==null?void 0:r.nickname}}},{type:"datetime",name:"updatedAt",interface:"datetime",uiSchema:{type:"string",title:e("Updated At"),"x-component":"DatePicker","x-component-props":{showTime:!0},"x-read-pretty":!0}},{type:"datetime",name:"expireAt",interface:"datetime",uiSchema:{type:"string",title:e("Lock Expires At"),"x-component":"DatePicker","x-component-props":{showTime:!0},"x-read-pretty":!0}},{name:"user",type:"belongsTo",target:"users",targetKey:"id",foreignKey:"userId",interface:"m2o",uiSchema:{type:"object",title:'{{t("User")}}',"x-component":"AssociationField","x-component-props":{fieldNames:{value:"id",label:"nickname"}},"x-read-pretty":!0}}]},j={type:"void","x-acl-action-props":{skipScopeCheck:!0},"x-acl-action":"userLocks:create","x-decorator":"FormBlockProvider","x-use-decorator-props":"useCreateFormBlockDecoratorProps","x-decorator-props":{dataSource:"main",collection:"userLocks"},"x-component":"CardItem",properties:{form:{type:"void","x-component":"FormV2","x-use-component-props":"useCreateFormBlockProps",properties:{actionBar:{type:"void","x-component":"ActionBar","x-component-props":{style:{marginBottom:"var(--tb-spacing)"}},properties:{submit:{title:'{{t("Submit")}}',"x-action":"submit","x-component":"Action","x-use-component-props":"useCreateActionProps","x-component-props":{type:"primary",htmlType:"submit"},type:"void"}}},userId:{type:"string",title:e("Username or Email"),required:!0,"x-component":"RemoteSelect","x-decorator":"FormItem","x-component-props":{placeholder:e("Please enter username, nickname or email"),fieldNames:{label:"nickname",value:"id"},service:{resource:"users",action:"list",params:{filter:{$or:[{lock:{id:{$notExists:!0}}},{lock:{expireAt:{$lt:"{{ new Date() }}"}}}]},pageSize:20,sort:["-id"],appends:["lock"]}},manual:!1}},expireAt:{type:"string",title:e("Lock Until"),required:!0,"x-component":"DatePicker","x-decorator":"FormItem","x-component-props":{showTime:!0,disabledDate:"{{ (current) => current && current < new Date() }}",placeholder:e("Please select lock expiration time")}}}}}},K={type:"void","x-action":"create","x-acl-action":"create",title:e("Lock New User"),"x-component":"Action","x-decorator":"ACLActionProvider","x-component-props":{openMode:"drawer",type:"primary",icon:"PlusOutlined"},"x-align":"right","x-acl-action-props":{skipScopeCheck:!0},properties:{drawer:{type:"void",title:e("Lock New User"),"x-component":"Action.Container","x-component-props":{className:"tb-action-popup"},properties:{form:j}}}},$={type:"void",title:'{{ t("Edit") }}',"x-action":"update","x-component":"Action.Link","x-component-props":{openMode:"drawer",icon:"EditOutlined"},"x-decorator":"ACLActionProvider",properties:{drawer:{type:"void",title:'{{ t("Edit") }}',"x-component":"Action.Container","x-component-props":{className:"tb-action-popup"},properties:{form:{type:"void","x-acl-action-props":{skipScopeCheck:!1},"x-decorator":"FormBlockProvider","x-use-decorator-props":"useEditFormBlockDecoratorProps","x-decorator-props":{dataSource:"main",collection:"userLocks",action:"get",useParams:"{{ useParamsFromRecord }}",params:{appends:["user"]}},"x-component":"CardItem",properties:{form:{type:"void","x-component":"FormV2","x-use-component-props":"useEditFormBlockProps",properties:{actionBar:{type:"void","x-component":"ActionBar","x-component-props":{style:{marginBottom:"var(--tb-spacing)"}},properties:{submit:{title:'{{t("Submit")}}',"x-action":"submit","x-component":"Action","x-use-component-props":"useUpdateActionProps","x-component-props":{type:"primary",htmlType:"submit"},"x-action-settings":{triggerWorkflows:[],onSuccess:{manualClose:!1,redirecting:!1,successMessage:'{{t("Updated successfully")}}'},skipValidator:!1},type:"void"}}},expireAt:{type:"date",title:e("Lock Until"),required:!0,"x-component":"DatePicker","x-component-props":{showTime:!0,disabledDate:"{{ (current) => current && current < new Date() }}",placeholder:e("Please select lock expiration time")},"x-decorator":"FormItem"}}}}}}}}},_={type:"void",properties:{userLocks:{type:"void","x-decorator":"TableBlockProvider","x-acl-action":"userLocks:list","x-use-decorator-props":"useTableBlockDecoratorProps","x-decorator-props":{dataSource:"main",collection:"userLocks",action:"list",params:{pageSize:20,appends:["user"]},rowKey:"id",showIndex:!0,dragSort:!1},"x-component":"CardItem",properties:{actions:{type:"void","x-component":"ActionBar","x-component-props":{style:{marginBottom:"var(--tb-spacing)"}},properties:{filter:{type:"void",title:'{{ t("Filter") }}',"x-action":"filter","x-component":"Filter.Action","x-use-component-props":"useFilterActionProps","x-component-props":{icon:"FilterOutlined"},"x-align":"left"},refresh:{type:"void",title:'{{ t("Refresh") }}',"x-action":"refresh","x-component":"Action","x-settings":"actionSettings:refresh","x-component-props":{icon:"ReloadOutlined"},"x-use-component-props":"useRefreshActionProps","x-align":"right"},create:K}},table:{type:"array","x-component":"TableV2","x-use-component-props":"useTableBlockProps","x-component-props":{rowKey:"id",rowSelection:{type:"checkbox"}},properties:{username:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{username:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},nickname:{type:"void","x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{nickname:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},updatedAt:{type:"void",title:e("Updated At"),"x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{updatedAt:{"x-collection-field":"userLocks.updatedAt","x-component":"CollectionField","x-component-props":{ellipsis:!0,showTime:!0},"x-read-pretty":!0,"x-decorator":null}}},expireAt:{type:"void",title:e("Lock Expires At"),"x-decorator":"TableV2.Column.Decorator","x-component":"TableV2.Column",properties:{expireAt:{"x-collection-field":"userLocks.expireAt","x-component":"CollectionField","x-component-props":{ellipsis:!0,showTime:!0},"x-read-pretty":!0,"x-decorator":null}}},actions:{type:"void",title:'{{ t("Actions") }}',"x-action-column":"actions","x-decorator":"TableV2.Column.ActionBar","x-component":"TableV2.Column","x-component-props":{width:200,fixed:"right"},properties:{actions:{type:"void","x-component":"Space","x-component-props":{split:"|"},properties:{edit:$,destroy:{type:"void",title:e("Unlock"),"x-action":"destroy","x-component":"Action.Link","x-component-props":{danger:!0,confirm:{title:e("Unlock User"),content:e("Are you sure you want to unlock this user?")}},"x-decorator":"ACLActionProvider","x-use-component-props":"useDestroyActionProps"}}}}}}}}}}},z=()=>n.jsx(t.ExtendCollectionsProvider,{collections:[R],children:n.jsx(t.SchemaComponent,{schema:_})});class A extends t.Plugin{load(){return y(this,null,function*(){this.app.systemSettingsManager.add("security.password-attempt",{icon:"SettingOutlined",title:e("Password policy"),Component:N,aclSnippet:"pm.security.password-attempt"}),this.app.systemSettingsManager.add("security.user-lock",{icon:"UserOutlined",title:e("User lock"),Component:z,aclSnippet:"pm.security.user-lock"}),this.app.systemSettingsManager.add("security.ip-filter",{icon:"GlobalOutlined",title:e("IP policy"),Component:S,aclSnippet:"pm.security.ip-filter"}),this.app.systemSettingsManager.add("security.password-strength",{icon:"LockOutlined",title:e("Password strength"),Component:M,aclSnippet:"pm.security.password-strength"}),this.app.systemSettingsManager.add("security.sign-in-fails",{icon:"HistoryOutlined",title:e("Sign-in fails history"),Component:E,aclSnippet:"pm.security.sign-in-fails"}),this.app.systemSettingsManager.add("security.password-validity-period",{icon:"LockOutlined",title:e("Password validity period"),Component:I,aclSnippet:"pm.security.password-validity-period"})})}}s.PasswordPolicyClient=A,s.default=A,Object.defineProperties(s,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ViewPasswordValidityPeriod: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export declare const getSchemaPasswordValidityPeriod: () => {
|
|
2
|
+
type: string;
|
|
3
|
+
properties: {
|
|
4
|
+
passwordValidityPeriod: {
|
|
5
|
+
type: string;
|
|
6
|
+
title: string;
|
|
7
|
+
'x-decorator': string;
|
|
8
|
+
'x-component': string;
|
|
9
|
+
'x-use-component-props': string;
|
|
10
|
+
properties: {
|
|
11
|
+
validityPeriod: {
|
|
12
|
+
type: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
'x-decorator': string;
|
|
16
|
+
'x-component': string;
|
|
17
|
+
'x-component-props': {
|
|
18
|
+
defaultValue: number;
|
|
19
|
+
options: {
|
|
20
|
+
label: string;
|
|
21
|
+
value: number;
|
|
22
|
+
}[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
footer: {
|
|
26
|
+
type: string;
|
|
27
|
+
'x-component': string;
|
|
28
|
+
properties: {
|
|
29
|
+
submit: {
|
|
30
|
+
title: string;
|
|
31
|
+
'x-component': string;
|
|
32
|
+
'x-component-props': {
|
|
33
|
+
type: string;
|
|
34
|
+
useAction: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
|
@@ -17,6 +17,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
17
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
18
|
var constants_exports = {};
|
|
19
19
|
__export(constants_exports, {
|
|
20
|
+
COLLECTION_PASSWORD_POLICY: () => COLLECTION_PASSWORD_POLICY,
|
|
20
21
|
LOCK_SECONDS: () => LOCK_SECONDS,
|
|
21
22
|
NAMESPACE: () => NAMESPACE,
|
|
22
23
|
PasswordStrengthLevel: () => PasswordStrengthLevel,
|
|
@@ -35,8 +36,10 @@ var PasswordStrengthLevel = /* @__PURE__ */ ((PasswordStrengthLevel2) => {
|
|
|
35
36
|
})(PasswordStrengthLevel || {});
|
|
36
37
|
const WINDOW_SECONDS = 300;
|
|
37
38
|
const LOCK_SECONDS = 1800;
|
|
39
|
+
const COLLECTION_PASSWORD_POLICY = "passwordPolicy";
|
|
38
40
|
// Annotate the CommonJS export names for ESM import in node:
|
|
39
41
|
0 && (module.exports = {
|
|
42
|
+
COLLECTION_PASSWORD_POLICY,
|
|
40
43
|
LOCK_SECONDS,
|
|
41
44
|
NAMESPACE,
|
|
42
45
|
PasswordStrengthLevel,
|
package/dist/externalVersion.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
"react": "18.3.1",
|
|
3
|
-
"@tachybase/client": "1.2.
|
|
4
|
-
"@tachybase/schema": "1.2.
|
|
3
|
+
"@tachybase/client": "1.2.11",
|
|
4
|
+
"@tachybase/schema": "1.2.11",
|
|
5
5
|
"antd": "5.22.5",
|
|
6
|
-
"@tachybase/server": "1.2.
|
|
7
|
-
"@tachybase/actions": "1.2.
|
|
8
|
-
"@tachybase/utils": "1.2.
|
|
9
|
-
"@tachybase/database": "1.2.
|
|
6
|
+
"@tachybase/server": "1.2.11",
|
|
7
|
+
"@tachybase/actions": "1.2.11",
|
|
8
|
+
"@tachybase/utils": "1.2.11",
|
|
9
|
+
"@tachybase/database": "1.2.11"
|
|
10
10
|
};
|
package/dist/locale/en-US.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Country": "Country",
|
|
12
12
|
"Days": "Days",
|
|
13
13
|
"Default value is 1800 seconds (30 minutes).": "Default value is 1800 seconds (30 minutes).",
|
|
14
|
+
"Default value is 30 days.": "Default value is 30 days.",
|
|
14
15
|
"Default value is 30 minutes.": "Default value is 30 minutes.",
|
|
15
16
|
"Default value is 300 seconds (5 minutes).": "Default value is 300 seconds (5 minutes).",
|
|
16
17
|
"Default value is 5 minutes.": "Default value is 5 minutes.",
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
"Must contain letters, numbers, and symbols": "Must contain letters, numbers, and symbols",
|
|
52
53
|
"Must contain numbers, uppercase and lowercase letters": "Must contain numbers, uppercase and lowercase letters",
|
|
53
54
|
"Must contain numbers, uppercase and lowercase letters, and symbols": "Must contain numbers, uppercase and lowercase letters, and symbols",
|
|
55
|
+
"Never expire": "Never expire",
|
|
54
56
|
"Nickname": "Nickname",
|
|
55
57
|
"No password strength requirements": "No password strength requirements",
|
|
56
58
|
"No restrictions": "No restrictions",
|
|
@@ -76,6 +78,8 @@
|
|
|
76
78
|
"Password strength: Medium": "Password strength: Medium",
|
|
77
79
|
"Password strength: Strong": "Password strength: Strong",
|
|
78
80
|
"Password strength: Very Strong": "Password strength: Very Strong",
|
|
81
|
+
"Password validity period": "Password validity period",
|
|
82
|
+
"Password validity period (days)": "Password validity period (days)",
|
|
79
83
|
"Please enter username, nickname or email": "Please enter username, nickname or email",
|
|
80
84
|
"Please input minimum password length": "Please input minimum password length",
|
|
81
85
|
"Please input password history count": "Please input password history count",
|
package/dist/locale/zh-CN.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"Default value is 300 seconds (5 minutes).": "默认值为300秒(5分钟)。",
|
|
16
16
|
"Default value is 5 minutes.": "默认值为5分钟。",
|
|
17
17
|
"Default value is 5. Set to 0 to disable.": "默认值为5。设置为0表示禁用。",
|
|
18
|
+
"Default value is never.": "密码过期后,用户将不能登录直到管理员重置密码。默认值为不限制。",
|
|
18
19
|
"Default value is off.": "默认值为关闭。",
|
|
19
20
|
"Edit": "编辑",
|
|
20
21
|
"Edit Lock": "编辑锁定",
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
"Must contain letters, numbers, and symbols": "必须包含字母、数字和符号",
|
|
52
53
|
"Must contain numbers, uppercase and lowercase letters": "必须包含数字、大写和小写字母",
|
|
53
54
|
"Must contain numbers, uppercase and lowercase letters, and symbols": "必须包含数字、大写和小写字母和符号",
|
|
55
|
+
"Never expire": "永不过期",
|
|
54
56
|
"Nickname": "昵称",
|
|
55
57
|
"No password strength requirements": "无密码强度要求",
|
|
56
58
|
"No restrictions": "无限制",
|
|
@@ -76,6 +78,8 @@
|
|
|
76
78
|
"Password strength: Medium": "密码强度:中等",
|
|
77
79
|
"Password strength: Strong": "密码强度:强",
|
|
78
80
|
"Password strength: Very Strong": "密码强度:非常强",
|
|
81
|
+
"Password validity period": "密码有效期",
|
|
82
|
+
"Password validity period (days)": "密码有效期(天)",
|
|
79
83
|
"Please enter username, nickname or email": "请输入用户名、昵称或邮箱",
|
|
80
84
|
"Please input minimum password length": "请输入最小密码长度",
|
|
81
85
|
"Please input password history count": "请输入密码历史数量",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"geoip-lite","version":"1.4.10","description":"A light weight native JavaScript implementation of GeoIP API from MaxMind","keywords":["geo","geoip","ip","ipv4","ipv6","geolookup","maxmind","geolite"],"homepage":"https://github.com/geoip-lite/node-geoip","author":"Philip Tellis <philip@bluesmoon.info> (http://bluesmoon.info/)","files":["lib/","data/","test/","scripts/"],"main":"lib/geoip.js","repository":{"type":"git","url":"git://github.com/geoip-lite/node-geoip.git"},"engines":{"node":">=10.3.0"},"scripts":{"pretest":"eslint .","test":"nodeunit --reporter=minimal test/tests.js","updatedb":"node scripts/updatedb.js","updatedb-debug":"node scripts/updatedb.js debug","updatedb-force":"node scripts/updatedb.js force"},"dependencies":{"async":"2.1 - 2.6.4","chalk":"4.1 - 4.1.2","iconv-lite":"0.4.13 - 0.6.3","ip-address":"5.8.9 - 5.9.4","lazy":"1.0.11","rimraf":"2.5.2 - 2.7.1","yauzl":"2.9.2 - 2.10.0"},"config":{"update":true},"devDependencies":{"eslint":"^5.12.1","nodeunit":"^0.11.2"},"license":"Apache-2.0","_lastModified":"2025-06-
|
|
1
|
+
{"name":"geoip-lite","version":"1.4.10","description":"A light weight native JavaScript implementation of GeoIP API from MaxMind","keywords":["geo","geoip","ip","ipv4","ipv6","geolookup","maxmind","geolite"],"homepage":"https://github.com/geoip-lite/node-geoip","author":"Philip Tellis <philip@bluesmoon.info> (http://bluesmoon.info/)","files":["lib/","data/","test/","scripts/"],"main":"lib/geoip.js","repository":{"type":"git","url":"git://github.com/geoip-lite/node-geoip.git"},"engines":{"node":">=10.3.0"},"scripts":{"pretest":"eslint .","test":"nodeunit --reporter=minimal test/tests.js","updatedb":"node scripts/updatedb.js","updatedb-debug":"node scripts/updatedb.js debug","updatedb-force":"node scripts/updatedb.js force"},"dependencies":{"async":"2.1 - 2.6.4","chalk":"4.1 - 4.1.2","iconv-lite":"0.4.13 - 0.6.3","ip-address":"5.8.9 - 5.9.4","lazy":"1.0.11","rimraf":"2.5.2 - 2.7.1","yauzl":"2.9.2 - 2.10.0"},"config":{"update":true},"devDependencies":{"eslint":"^5.12.1","nodeunit":"^0.11.2"},"license":"Apache-2.0","_lastModified":"2025-06-19T15:41:15.159Z"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Context, Next } from '@tachybase/actions';
|
|
2
|
+
import { PasswordPolicyService } from '../services/PasswordPolicyService';
|
|
3
|
+
export declare class PasswordPolicyController {
|
|
4
|
+
passwordPolicyService: PasswordPolicyService;
|
|
5
|
+
getConfig(ctx: Context, next: Next): Promise<any>;
|
|
6
|
+
setConfiguration(ctx: Context, next: Next): Promise<any>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
|
+
var __decoratorStart = (base) => [, , , __create((base == null ? void 0 : base[__knownSymbol("metadata")]) ?? null)];
|
|
26
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
27
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
28
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
29
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
30
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
31
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
32
|
+
return value;
|
|
33
|
+
};
|
|
34
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
35
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
36
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
37
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
38
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
39
|
+
return __privateGet(this, extra);
|
|
40
|
+
}, set [name](x) {
|
|
41
|
+
return __privateSet(this, extra, x);
|
|
42
|
+
} }, name));
|
|
43
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
44
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
45
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
46
|
+
if (k) {
|
|
47
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
48
|
+
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
49
|
+
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
50
|
+
}
|
|
51
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
|
52
|
+
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
53
|
+
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
54
|
+
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
55
|
+
}
|
|
56
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
57
|
+
};
|
|
58
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
59
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
60
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
61
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
62
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
63
|
+
var PasswordPolicyController_exports = {};
|
|
64
|
+
__export(PasswordPolicyController_exports, {
|
|
65
|
+
PasswordPolicyController: () => PasswordPolicyController
|
|
66
|
+
});
|
|
67
|
+
module.exports = __toCommonJS(PasswordPolicyController_exports);
|
|
68
|
+
var import_utils = require("@tachybase/utils");
|
|
69
|
+
var import_constants = require("../../constants");
|
|
70
|
+
var import_PasswordPolicyService = require("../services/PasswordPolicyService");
|
|
71
|
+
var _setConfiguration_dec, _getConfig_dec, _passwordPolicyService_dec, _PasswordPolicyController_decorators, _init;
|
|
72
|
+
_PasswordPolicyController_decorators = [(0, import_utils.Controller)(import_constants.COLLECTION_PASSWORD_POLICY)], _passwordPolicyService_dec = [(0, import_utils.Inject)(() => import_PasswordPolicyService.PasswordPolicyService)], _getConfig_dec = [(0, import_utils.Action)("get")], _setConfiguration_dec = [(0, import_utils.Action)("set")];
|
|
73
|
+
class PasswordPolicyController {
|
|
74
|
+
constructor() {
|
|
75
|
+
__runInitializers(_init, 5, this);
|
|
76
|
+
this.passwordPolicyService = __runInitializers(_init, 8, this), __runInitializers(_init, 11, this);
|
|
77
|
+
}
|
|
78
|
+
async getConfig(ctx, next) {
|
|
79
|
+
const repo = ctx.db.getRepository(import_constants.COLLECTION_PASSWORD_POLICY);
|
|
80
|
+
const data = await repo.findOne();
|
|
81
|
+
ctx.body = data;
|
|
82
|
+
return next();
|
|
83
|
+
}
|
|
84
|
+
async setConfiguration(ctx, next) {
|
|
85
|
+
const params = ctx.action.params;
|
|
86
|
+
let transaction;
|
|
87
|
+
try {
|
|
88
|
+
const transaction2 = await ctx.db.sequelize.transaction();
|
|
89
|
+
const repo = ctx.db.getRepository(import_constants.COLLECTION_PASSWORD_POLICY);
|
|
90
|
+
const existOne = await repo.findOne({
|
|
91
|
+
transaction: transaction2
|
|
92
|
+
});
|
|
93
|
+
let data;
|
|
94
|
+
if (!existOne) {
|
|
95
|
+
data = await repo.create({
|
|
96
|
+
values: params.values,
|
|
97
|
+
transaction: transaction2
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
data = await repo.update({
|
|
101
|
+
filterByTk: params.values.id,
|
|
102
|
+
values: params.values,
|
|
103
|
+
transaction: transaction2
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
await transaction2.commit();
|
|
107
|
+
ctx.body = data;
|
|
108
|
+
} catch (err) {
|
|
109
|
+
ctx.app.logger.error("put password policy config error", err);
|
|
110
|
+
transaction == null ? void 0 : transaction.rollback();
|
|
111
|
+
}
|
|
112
|
+
return next();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
_init = __decoratorStart(null);
|
|
116
|
+
__decorateElement(_init, 1, "getConfig", _getConfig_dec, PasswordPolicyController);
|
|
117
|
+
__decorateElement(_init, 1, "setConfiguration", _setConfiguration_dec, PasswordPolicyController);
|
|
118
|
+
__decorateElement(_init, 5, "passwordPolicyService", _passwordPolicyService_dec, PasswordPolicyController);
|
|
119
|
+
PasswordPolicyController = __decorateElement(_init, 0, "PasswordPolicyController", _PasswordPolicyController_decorators, PasswordPolicyController);
|
|
120
|
+
__runInitializers(_init, 1, PasswordPolicyController);
|
|
121
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
122
|
+
0 && (module.exports = {
|
|
123
|
+
PasswordPolicyController
|
|
124
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var passwordPolicy_exports = {};
|
|
19
|
+
__export(passwordPolicy_exports, {
|
|
20
|
+
default: () => passwordPolicy_default
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(passwordPolicy_exports);
|
|
23
|
+
var import_database = require("@tachybase/database");
|
|
24
|
+
var import_constants = require("../../constants");
|
|
25
|
+
var passwordPolicy_default = (0, import_database.defineCollection)({
|
|
26
|
+
dumpRules: {
|
|
27
|
+
group: "user"
|
|
28
|
+
},
|
|
29
|
+
name: import_constants.COLLECTION_PASSWORD_POLICY,
|
|
30
|
+
createdAt: true,
|
|
31
|
+
updatedAt: true,
|
|
32
|
+
createdBy: true,
|
|
33
|
+
updatedBy: true,
|
|
34
|
+
fields: [
|
|
35
|
+
{
|
|
36
|
+
type: "integer",
|
|
37
|
+
name: "validityPeriod"
|
|
38
|
+
// 密码有效期:天
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
});
|
package/dist/server/plugin.js
CHANGED
|
@@ -69,11 +69,13 @@ module.exports = __toCommonJS(plugin_exports);
|
|
|
69
69
|
var import_server = require("@tachybase/server");
|
|
70
70
|
var import_IpFilterController = require("./actions/IpFilterController");
|
|
71
71
|
var import_PasswordAttemptController = require("./actions/PasswordAttemptController");
|
|
72
|
+
var import_PasswordPolicyController = require("./actions/PasswordPolicyController");
|
|
72
73
|
var import_PasswordStrengthController = require("./actions/PasswordStrengthController");
|
|
73
74
|
var import_SignInFailsController = require("./actions/SignInFailsController");
|
|
74
75
|
var import_UserLocksController = require("./actions/UserLocksController");
|
|
75
76
|
var import_IPFilterService = require("./services/IPFilterService");
|
|
76
77
|
var import_PasswordAttemptService = require("./services/PasswordAttemptService");
|
|
78
|
+
var import_PasswordPolicyService = require("./services/PasswordPolicyService");
|
|
77
79
|
var import_PasswordStrengthService = require("./services/PasswordStrengthService");
|
|
78
80
|
var _PluginPasswordPolicyServer_decorators, _init, _a;
|
|
79
81
|
_PluginPasswordPolicyServer_decorators = [(0, import_server.InjectedPlugin)({
|
|
@@ -82,9 +84,10 @@ _PluginPasswordPolicyServer_decorators = [(0, import_server.InjectedPlugin)({
|
|
|
82
84
|
import_UserLocksController.UserLocksController,
|
|
83
85
|
import_IpFilterController.IpFilterController,
|
|
84
86
|
import_PasswordStrengthController.PasswordStrengthController,
|
|
85
|
-
import_SignInFailsController.SignInFailsController
|
|
87
|
+
import_SignInFailsController.SignInFailsController,
|
|
88
|
+
import_PasswordPolicyController.PasswordPolicyController
|
|
86
89
|
],
|
|
87
|
-
Services: [import_PasswordAttemptService.PasswordAttemptService, import_IPFilterService.IPFilterService, import_PasswordStrengthService.PasswordStrengthService]
|
|
90
|
+
Services: [import_PasswordAttemptService.PasswordAttemptService, import_IPFilterService.IPFilterService, import_PasswordStrengthService.PasswordStrengthService, import_PasswordPolicyService.PasswordPolicyService]
|
|
88
91
|
})];
|
|
89
92
|
class PluginPasswordPolicyServer extends (_a = import_server.Plugin) {
|
|
90
93
|
async load() {
|
|
@@ -108,6 +111,10 @@ class PluginPasswordPolicyServer extends (_a = import_server.Plugin) {
|
|
|
108
111
|
name: `pm.security.sign-in-fails`,
|
|
109
112
|
actions: ["signInFails:*"]
|
|
110
113
|
});
|
|
114
|
+
this.app.acl.registerSnippet({
|
|
115
|
+
name: `pm.security.password-policy`,
|
|
116
|
+
actions: ["passwordPolicy:*"]
|
|
117
|
+
});
|
|
111
118
|
this.app.acl.addFixedParams("userLocks", "list", () => {
|
|
112
119
|
return {
|
|
113
120
|
filter: {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Database from '@tachybase/database';
|
|
2
|
+
import { Application } from '@tachybase/server';
|
|
3
|
+
export declare class PasswordPolicyService {
|
|
4
|
+
db: Database;
|
|
5
|
+
app: Application;
|
|
6
|
+
private logger;
|
|
7
|
+
private config;
|
|
8
|
+
private failureRecords;
|
|
9
|
+
private readonly CACHE_PREFIX;
|
|
10
|
+
private readonly CACHE_TTL;
|
|
11
|
+
load(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* 获取用户锁定信息的缓存键
|
|
14
|
+
* @param userId 用户ID
|
|
15
|
+
* @returns 缓存键
|
|
16
|
+
*/
|
|
17
|
+
private getUserLockCacheKey;
|
|
18
|
+
/**
|
|
19
|
+
* 初始化被锁定用户的缓存
|
|
20
|
+
*/
|
|
21
|
+
private initLockedUsersCache;
|
|
22
|
+
/**
|
|
23
|
+
* 设置监听userLocks表变动的事件
|
|
24
|
+
*/
|
|
25
|
+
private setupLockedUsersListener;
|
|
26
|
+
/**
|
|
27
|
+
* 清空用户的登录失败记录
|
|
28
|
+
* @param userId 用户ID
|
|
29
|
+
*/
|
|
30
|
+
private clearUserFailRecords;
|
|
31
|
+
/**
|
|
32
|
+
* 检查用户是否被锁定
|
|
33
|
+
* @param userId 用户ID
|
|
34
|
+
* @returns 如果用户被锁定,返回true;否则返回false
|
|
35
|
+
*/
|
|
36
|
+
private isUserLocked;
|
|
37
|
+
/**
|
|
38
|
+
* 从数据库检查并缓存用户锁定状态
|
|
39
|
+
* @param userId 用户ID
|
|
40
|
+
* @returns 如果用户被锁定,返回true;否则返回false
|
|
41
|
+
*/
|
|
42
|
+
private checkAndCacheLockedUser;
|
|
43
|
+
/**
|
|
44
|
+
* 在后台刷新用户锁定缓存
|
|
45
|
+
* @param userId 用户ID
|
|
46
|
+
*/
|
|
47
|
+
private refreshLockedUserCache;
|
|
48
|
+
refreshConfig(config: any): Promise<void>;
|
|
49
|
+
addMiddleWare(): void;
|
|
50
|
+
/**
|
|
51
|
+
* 从数据库加载最近的失败记录到内存
|
|
52
|
+
*/
|
|
53
|
+
private loadRecentRecords;
|
|
54
|
+
/**
|
|
55
|
+
* 获取IP地址的地理位置信息
|
|
56
|
+
* @param ip IP地址
|
|
57
|
+
* @returns 地理位置信息
|
|
58
|
+
*/
|
|
59
|
+
private getGeoLocation;
|
|
60
|
+
/**
|
|
61
|
+
* 记录登录失败
|
|
62
|
+
* @param username 用户名
|
|
63
|
+
*/
|
|
64
|
+
recordFailedAttempt(user: any, ip: string): Promise<void>;
|
|
65
|
+
private recordFailedAttemptToDb;
|
|
66
|
+
/**
|
|
67
|
+
* 获取最近的失败次数(从内存缓存中获取)
|
|
68
|
+
*/
|
|
69
|
+
private getRecentFailureCount;
|
|
70
|
+
/**
|
|
71
|
+
* 重置用户的失败记录
|
|
72
|
+
* @param username 用户名
|
|
73
|
+
*/
|
|
74
|
+
resetFailedAttempts(userId: number): Promise<void>;
|
|
75
|
+
}
|
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
8
|
+
var __typeError = (msg) => {
|
|
9
|
+
throw TypeError(msg);
|
|
10
|
+
};
|
|
11
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
26
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
27
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
28
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
29
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
30
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
31
|
+
mod
|
|
32
|
+
));
|
|
33
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
34
|
+
var __decoratorStart = (base) => [, , , __create((base == null ? void 0 : base[__knownSymbol("metadata")]) ?? null)];
|
|
35
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
36
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
37
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
38
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
39
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
40
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
41
|
+
return value;
|
|
42
|
+
};
|
|
43
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
44
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
45
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
46
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
47
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
48
|
+
return __privateGet(this, extra);
|
|
49
|
+
}, set [name](x) {
|
|
50
|
+
return __privateSet(this, extra, x);
|
|
51
|
+
} }, name));
|
|
52
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
53
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
54
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
55
|
+
if (k) {
|
|
56
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
57
|
+
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
58
|
+
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
59
|
+
}
|
|
60
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
|
61
|
+
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
62
|
+
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
63
|
+
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
64
|
+
}
|
|
65
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
66
|
+
};
|
|
67
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
68
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
69
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
70
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
71
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
72
|
+
var PasswordPolicyService_exports = {};
|
|
73
|
+
__export(PasswordPolicyService_exports, {
|
|
74
|
+
PasswordPolicyService: () => PasswordPolicyService
|
|
75
|
+
});
|
|
76
|
+
module.exports = __toCommonJS(PasswordPolicyService_exports);
|
|
77
|
+
var import_utils = require("@tachybase/utils");
|
|
78
|
+
var geoip = __toESM(require("geoip-lite"));
|
|
79
|
+
var import_constants = require("../../constants");
|
|
80
|
+
var _logger_dec, _app_dec, _db_dec, _PasswordPolicyService_decorators, _init;
|
|
81
|
+
_PasswordPolicyService_decorators = [(0, import_utils.Service)()], _db_dec = [(0, import_utils.Db)()], _app_dec = [(0, import_utils.App)()], _logger_dec = [(0, import_utils.InjectLog)()];
|
|
82
|
+
class PasswordPolicyService {
|
|
83
|
+
constructor() {
|
|
84
|
+
this.db = __runInitializers(_init, 8, this), __runInitializers(_init, 11, this);
|
|
85
|
+
this.app = __runInitializers(_init, 12, this), __runInitializers(_init, 15, this);
|
|
86
|
+
this.logger = __runInitializers(_init, 16, this), __runInitializers(_init, 19, this);
|
|
87
|
+
this.config = void 0;
|
|
88
|
+
// 内存缓存,用于存储最近的失败记录
|
|
89
|
+
this.failureRecords = /* @__PURE__ */ new Map();
|
|
90
|
+
// 缓存前缀
|
|
91
|
+
this.CACHE_PREFIX = import_constants.COLLECTION_PASSWORD_POLICY;
|
|
92
|
+
// 缓存过期时间(毫秒)
|
|
93
|
+
this.CACHE_TTL = 5 * 60 * 1e3;
|
|
94
|
+
}
|
|
95
|
+
// 5分钟
|
|
96
|
+
async load() {
|
|
97
|
+
this.addMiddleWare();
|
|
98
|
+
this.app.on("afterStart", async () => {
|
|
99
|
+
const config = await this.db.getRepository(import_constants.COLLECTION_PASSWORD_POLICY).findOne();
|
|
100
|
+
await this.refreshConfig(config);
|
|
101
|
+
await this.initLockedUsersCache();
|
|
102
|
+
this.setupLockedUsersListener();
|
|
103
|
+
});
|
|
104
|
+
this.db.on(`${import_constants.COLLECTION_PASSWORD_POLICY}.afterSave`, async (model) => {
|
|
105
|
+
await this.refreshConfig(model);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 获取用户锁定信息的缓存键
|
|
110
|
+
* @param userId 用户ID
|
|
111
|
+
* @returns 缓存键
|
|
112
|
+
*/
|
|
113
|
+
getUserLockCacheKey(userId) {
|
|
114
|
+
return `${this.CACHE_PREFIX}:locked:${userId}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 初始化被锁定用户的缓存
|
|
118
|
+
*/
|
|
119
|
+
async initLockedUsersCache() {
|
|
120
|
+
try {
|
|
121
|
+
const now = /* @__PURE__ */ new Date();
|
|
122
|
+
const lockedUsersRepo = this.db.getRepository("userLocks");
|
|
123
|
+
const lockedUsers = await lockedUsersRepo.find({
|
|
124
|
+
filter: {
|
|
125
|
+
expireAt: {
|
|
126
|
+
$gt: now
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
for (const user of lockedUsers) {
|
|
131
|
+
const userId = user.get("userId");
|
|
132
|
+
const expireAt = user.get("expireAt");
|
|
133
|
+
await this.app.cache.set(
|
|
134
|
+
this.getUserLockCacheKey(userId),
|
|
135
|
+
{
|
|
136
|
+
id: userId,
|
|
137
|
+
expireAt,
|
|
138
|
+
lastChecked: now
|
|
139
|
+
},
|
|
140
|
+
this.CACHE_TTL
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
this.logger.info(`Loaded ${lockedUsers.length} locked users into cache`);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.logger.error("Failed to load locked users into cache:", error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 设置监听userLocks表变动的事件
|
|
150
|
+
*/
|
|
151
|
+
setupLockedUsersListener() {
|
|
152
|
+
this.app.db.on("userLocks.afterCreate", async (model) => {
|
|
153
|
+
const userId = model.get("userId");
|
|
154
|
+
const expireAt = model.get("expireAt");
|
|
155
|
+
await this.app.cache.set(
|
|
156
|
+
this.getUserLockCacheKey(userId),
|
|
157
|
+
{
|
|
158
|
+
id: userId,
|
|
159
|
+
expireAt,
|
|
160
|
+
lastChecked: /* @__PURE__ */ new Date()
|
|
161
|
+
},
|
|
162
|
+
this.CACHE_TTL
|
|
163
|
+
);
|
|
164
|
+
this.logger.info(`Added user ${userId} to locked users cache`);
|
|
165
|
+
});
|
|
166
|
+
this.app.db.on("userLocks.afterUpdate", async (model) => {
|
|
167
|
+
const userId = model.get("userId");
|
|
168
|
+
if (!userId && model.previous("userId")) {
|
|
169
|
+
const userId2 = model.previous("userId");
|
|
170
|
+
await this.app.cache.del(this.getUserLockCacheKey(userId2));
|
|
171
|
+
await this.clearUserFailRecords(userId2);
|
|
172
|
+
this.logger.info(`Removed user ${userId2} from locked users cache (deleted)`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const expireAt = model.get("expireAt");
|
|
176
|
+
const now = /* @__PURE__ */ new Date();
|
|
177
|
+
if (expireAt > now) {
|
|
178
|
+
await this.app.cache.set(
|
|
179
|
+
this.getUserLockCacheKey(userId),
|
|
180
|
+
{
|
|
181
|
+
id: userId,
|
|
182
|
+
expireAt,
|
|
183
|
+
lastChecked: now
|
|
184
|
+
},
|
|
185
|
+
this.CACHE_TTL
|
|
186
|
+
);
|
|
187
|
+
this.logger.info(`Updated user ${userId} in locked users cache`);
|
|
188
|
+
} else {
|
|
189
|
+
await this.app.cache.del(this.getUserLockCacheKey(userId));
|
|
190
|
+
await this.clearUserFailRecords(userId);
|
|
191
|
+
this.logger.info(`Removed user ${userId} from locked users cache (expired)`);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
this.app.db.on("userLocks.afterDestroy", async (model) => {
|
|
195
|
+
const userId = model.get("userId");
|
|
196
|
+
await this.app.cache.del(this.getUserLockCacheKey(userId));
|
|
197
|
+
await this.clearUserFailRecords(userId);
|
|
198
|
+
this.logger.info(`Removed user ${userId} from locked users cache (deleted)`);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 清空用户的登录失败记录
|
|
203
|
+
* @param userId 用户ID
|
|
204
|
+
*/
|
|
205
|
+
async clearUserFailRecords(userId) {
|
|
206
|
+
try {
|
|
207
|
+
await this.db.getRepository("signInFails").update({
|
|
208
|
+
filter: {
|
|
209
|
+
userId
|
|
210
|
+
},
|
|
211
|
+
values: {
|
|
212
|
+
status: false
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
this.failureRecords.delete(userId);
|
|
216
|
+
this.logger.info(`Cleared sign-in failure records for user ${userId}`);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
this.logger.error(`Failed to clear sign-in failure records for user ${userId}:`, error);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 检查用户是否被锁定
|
|
223
|
+
* @param userId 用户ID
|
|
224
|
+
* @returns 如果用户被锁定,返回true;否则返回false
|
|
225
|
+
*/
|
|
226
|
+
async isUserLocked(userId) {
|
|
227
|
+
const now = /* @__PURE__ */ new Date();
|
|
228
|
+
const cacheKey = this.getUserLockCacheKey(userId);
|
|
229
|
+
const cachedInfo = await this.app.cache.get(cacheKey);
|
|
230
|
+
if (cachedInfo) {
|
|
231
|
+
const isCacheExpired = now.getTime() - cachedInfo.lastChecked.getTime() > this.CACHE_TTL;
|
|
232
|
+
if (isCacheExpired) {
|
|
233
|
+
this.refreshLockedUserCache(userId);
|
|
234
|
+
}
|
|
235
|
+
if (!cachedInfo.expireAt) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (cachedInfo.expireAt > now) {
|
|
239
|
+
return true;
|
|
240
|
+
} else {
|
|
241
|
+
cachedInfo.expireAt = null;
|
|
242
|
+
cachedInfo.lastChecked = now;
|
|
243
|
+
await this.app.cache.set(cacheKey, cachedInfo, this.CACHE_TTL);
|
|
244
|
+
await this.clearUserFailRecords(userId);
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return await this.checkAndCacheLockedUser(userId);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* 从数据库检查并缓存用户锁定状态
|
|
252
|
+
* @param userId 用户ID
|
|
253
|
+
* @returns 如果用户被锁定,返回true;否则返回false
|
|
254
|
+
*/
|
|
255
|
+
async checkAndCacheLockedUser(userId) {
|
|
256
|
+
var _a;
|
|
257
|
+
try {
|
|
258
|
+
const now = /* @__PURE__ */ new Date();
|
|
259
|
+
const userRepository = this.db.getRepository("users");
|
|
260
|
+
const user = await userRepository.findOne({
|
|
261
|
+
fields: ["id"],
|
|
262
|
+
filter: { id: userId },
|
|
263
|
+
appends: ["lock"]
|
|
264
|
+
});
|
|
265
|
+
const cacheKey = this.getUserLockCacheKey(userId);
|
|
266
|
+
if (user && ((_a = user.lock) == null ? void 0 : _a.expireAt) && user.lock.expireAt > now) {
|
|
267
|
+
await this.app.cache.set(
|
|
268
|
+
cacheKey,
|
|
269
|
+
{
|
|
270
|
+
id: userId,
|
|
271
|
+
expireAt: user.lock.expireAt,
|
|
272
|
+
lastChecked: now
|
|
273
|
+
},
|
|
274
|
+
this.CACHE_TTL
|
|
275
|
+
);
|
|
276
|
+
return true;
|
|
277
|
+
} else {
|
|
278
|
+
await this.app.cache.set(
|
|
279
|
+
cacheKey,
|
|
280
|
+
{
|
|
281
|
+
id: userId,
|
|
282
|
+
expireAt: null,
|
|
283
|
+
// null表示未被锁定
|
|
284
|
+
lastChecked: now
|
|
285
|
+
},
|
|
286
|
+
this.CACHE_TTL
|
|
287
|
+
);
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
this.logger.error(`Error checking locked status for user ${userId}:`, error);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* 在后台刷新用户锁定缓存
|
|
297
|
+
* @param userId 用户ID
|
|
298
|
+
*/
|
|
299
|
+
async refreshLockedUserCache(userId) {
|
|
300
|
+
var _a;
|
|
301
|
+
try {
|
|
302
|
+
const now = /* @__PURE__ */ new Date();
|
|
303
|
+
const userRepository = this.db.getRepository("users");
|
|
304
|
+
const user = await userRepository.findOne({
|
|
305
|
+
fields: ["id"],
|
|
306
|
+
filter: { id: userId },
|
|
307
|
+
appends: ["lock"]
|
|
308
|
+
});
|
|
309
|
+
const cacheKey = this.getUserLockCacheKey(userId);
|
|
310
|
+
if (user && ((_a = user.lock) == null ? void 0 : _a.expireAt) && user.lock.expireAt > now) {
|
|
311
|
+
await this.app.cache.set(
|
|
312
|
+
cacheKey,
|
|
313
|
+
{
|
|
314
|
+
id: userId,
|
|
315
|
+
expireAt: user.lock.expireAt,
|
|
316
|
+
lastChecked: now
|
|
317
|
+
},
|
|
318
|
+
this.CACHE_TTL
|
|
319
|
+
);
|
|
320
|
+
} else {
|
|
321
|
+
const existingInfo = await this.app.cache.get(cacheKey);
|
|
322
|
+
if (existingInfo) {
|
|
323
|
+
existingInfo.expireAt = null;
|
|
324
|
+
existingInfo.lastChecked = now;
|
|
325
|
+
await this.app.cache.set(cacheKey, existingInfo, this.CACHE_TTL);
|
|
326
|
+
} else {
|
|
327
|
+
await this.app.cache.set(
|
|
328
|
+
cacheKey,
|
|
329
|
+
{
|
|
330
|
+
id: userId,
|
|
331
|
+
expireAt: null,
|
|
332
|
+
lastChecked: now
|
|
333
|
+
},
|
|
334
|
+
this.CACHE_TTL
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
this.logger.error(`Error refreshing locked cache for user ${userId}:`, error);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async refreshConfig(config) {
|
|
343
|
+
this.config = {
|
|
344
|
+
windowSeconds: (config == null ? void 0 : config.get("windowSeconds")) || import_constants.WINDOW_SECONDS,
|
|
345
|
+
// 默认5分钟
|
|
346
|
+
maxAttempts: (config == null ? void 0 : config.get("maxAttempts")) ?? 0,
|
|
347
|
+
// 默认0,表示不启用防护
|
|
348
|
+
lockSeconds: (config == null ? void 0 : config.get("lockSeconds")) || import_constants.LOCK_SECONDS,
|
|
349
|
+
// 默认30分钟
|
|
350
|
+
strictLock: (config == null ? void 0 : config.get("strictLock")) || false
|
|
351
|
+
// 锁定时候禁止任意api
|
|
352
|
+
};
|
|
353
|
+
if (this.config.maxAttempts > 0) {
|
|
354
|
+
await this.loadRecentRecords();
|
|
355
|
+
} else {
|
|
356
|
+
this.logger.info("Sign-in failure protection is disabled (maxAttempts = 0)");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
addMiddleWare() {
|
|
360
|
+
this.app.resourcer.use(
|
|
361
|
+
async (ctx, next) => {
|
|
362
|
+
var _a;
|
|
363
|
+
const { resourceName, actionName } = ctx.action.params;
|
|
364
|
+
if (resourceName === "auth" && actionName === "signIn") {
|
|
365
|
+
const { account, password, email } = ctx.action.params.values;
|
|
366
|
+
if (account && password) {
|
|
367
|
+
const filter = email ? { email } : {
|
|
368
|
+
$or: [{ username: account }, { email: account }]
|
|
369
|
+
};
|
|
370
|
+
const userRepository = ctx.db.getRepository("users");
|
|
371
|
+
const user = await userRepository.findOne({
|
|
372
|
+
fields: ["id", "password"],
|
|
373
|
+
filter,
|
|
374
|
+
appends: ["lock"]
|
|
375
|
+
});
|
|
376
|
+
if (user) {
|
|
377
|
+
if (((_a = user.lock) == null ? void 0 : _a.expireAt) && user.lock.expireAt > /* @__PURE__ */ new Date()) {
|
|
378
|
+
ctx.throw(403, ctx.t("User has been locked", { ns: import_constants.NAMESPACE }));
|
|
379
|
+
}
|
|
380
|
+
const field = userRepository.collection.getField("password");
|
|
381
|
+
const valid = await field.verify(password, user.password);
|
|
382
|
+
if (!valid) {
|
|
383
|
+
await this.recordFailedAttempt(user, ctx.state.clientIp);
|
|
384
|
+
} else {
|
|
385
|
+
await this.resetFailedAttempts(user.id);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
await next();
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
tag: "lockUserByPasswordPolicy",
|
|
394
|
+
before: "auth"
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
this.app.resourcer.use(
|
|
398
|
+
async (ctx, next) => {
|
|
399
|
+
if (this.config.strictLock && ctx.state.currentUser) {
|
|
400
|
+
const userId = ctx.state.currentUser.id;
|
|
401
|
+
const isLocked = await this.isUserLocked(userId);
|
|
402
|
+
if (isLocked) {
|
|
403
|
+
ctx.throw(403, ctx.t("User has been locked", { ns: import_constants.NAMESPACE }));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
await next();
|
|
407
|
+
},
|
|
408
|
+
{ tag: "lockAllResource", after: "auth", before: "acl" }
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* 从数据库加载最近的失败记录到内存
|
|
413
|
+
*/
|
|
414
|
+
async loadRecentRecords() {
|
|
415
|
+
try {
|
|
416
|
+
const cutoffTime = /* @__PURE__ */ new Date();
|
|
417
|
+
cutoffTime.setSeconds(cutoffTime.getSeconds() - Math.max(this.config.windowSeconds, this.config.lockSeconds));
|
|
418
|
+
const records = await this.db.getRepository("signInFails").find({
|
|
419
|
+
filter: {
|
|
420
|
+
createdAt: {
|
|
421
|
+
$gt: cutoffTime
|
|
422
|
+
},
|
|
423
|
+
status: true
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
this.failureRecords.clear();
|
|
427
|
+
records.forEach((record) => {
|
|
428
|
+
const userId = record.get("userId");
|
|
429
|
+
const createdAt = record.get("createdAt");
|
|
430
|
+
const userRecords = this.failureRecords.get(userId) || [];
|
|
431
|
+
userRecords.push({ userId, createdAt });
|
|
432
|
+
this.failureRecords.set(userId, userRecords);
|
|
433
|
+
});
|
|
434
|
+
this.logger.info(`Loaded ${records.length} recent sign-in failure records into cache`);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
this.logger.error("Failed to load recent sign-in failure records:", error);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* 获取IP地址的地理位置信息
|
|
441
|
+
* @param ip IP地址
|
|
442
|
+
* @returns 地理位置信息
|
|
443
|
+
*/
|
|
444
|
+
getGeoLocation(ip) {
|
|
445
|
+
try {
|
|
446
|
+
if (!ip || ip === "127.0.0.1" || ip === "localhost" || ip.startsWith("192.168.") || ip.startsWith("10.")) {
|
|
447
|
+
return { country: "Local", region: "Local", city: "Local" };
|
|
448
|
+
}
|
|
449
|
+
const geo = geoip.lookup(ip);
|
|
450
|
+
if (!geo) {
|
|
451
|
+
return {};
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
country: geo.country,
|
|
455
|
+
region: geo.region,
|
|
456
|
+
city: geo.city
|
|
457
|
+
};
|
|
458
|
+
} catch (error) {
|
|
459
|
+
this.logger.error(`Failed to get geo location for IP ${ip}:`, error);
|
|
460
|
+
return {};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* 记录登录失败
|
|
465
|
+
* @param username 用户名
|
|
466
|
+
*/
|
|
467
|
+
async recordFailedAttempt(user, ip) {
|
|
468
|
+
var _a, _b;
|
|
469
|
+
try {
|
|
470
|
+
const now = /* @__PURE__ */ new Date();
|
|
471
|
+
if (this.config.maxAttempts === 0) {
|
|
472
|
+
await this.recordFailedAttemptToDb(user, ip, now, false);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (((_a = user.lock) == null ? void 0 : _a.expireAt) && now > user.lock.expireAt) {
|
|
476
|
+
await this.resetFailedAttempts(user.id);
|
|
477
|
+
} else if (this.getRecentFailureCount(user.id) + 1 >= this.config.maxAttempts) {
|
|
478
|
+
if (!((_b = user.lock) == null ? void 0 : _b.expireAt) || now > user.lock.expireAt) {
|
|
479
|
+
const lockExpireAt = new Date(now);
|
|
480
|
+
lockExpireAt.setSeconds(lockExpireAt.getSeconds() + this.config.lockSeconds);
|
|
481
|
+
await this.db.sequelize.transaction(async (transaction) => {
|
|
482
|
+
const existOne = await this.db.getRepository("userLocks").findOne({
|
|
483
|
+
filter: {
|
|
484
|
+
userId: user.id
|
|
485
|
+
},
|
|
486
|
+
transaction
|
|
487
|
+
});
|
|
488
|
+
if (existOne) {
|
|
489
|
+
await existOne.update(
|
|
490
|
+
{
|
|
491
|
+
expireAt: lockExpireAt
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
transaction
|
|
495
|
+
}
|
|
496
|
+
);
|
|
497
|
+
} else {
|
|
498
|
+
await this.db.getRepository("userLocks").create({
|
|
499
|
+
values: {
|
|
500
|
+
userId: user.id,
|
|
501
|
+
expireAt: lockExpireAt
|
|
502
|
+
},
|
|
503
|
+
transaction
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
await this.app.cache.set(
|
|
508
|
+
this.getUserLockCacheKey(user.id),
|
|
509
|
+
{
|
|
510
|
+
id: user.id,
|
|
511
|
+
expireAt: lockExpireAt,
|
|
512
|
+
lastChecked: now
|
|
513
|
+
},
|
|
514
|
+
this.CACHE_TTL
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const record = await this.recordFailedAttemptToDb(user, ip, now, true);
|
|
519
|
+
if (this.failureRecords.get(user.id)) {
|
|
520
|
+
this.failureRecords.get(user.id).push({
|
|
521
|
+
userId: user.id,
|
|
522
|
+
createdAt: record.get("createdAt")
|
|
523
|
+
});
|
|
524
|
+
} else {
|
|
525
|
+
this.failureRecords.set(user.id, [
|
|
526
|
+
{
|
|
527
|
+
userId: user.id,
|
|
528
|
+
createdAt: record.get("createdAt")
|
|
529
|
+
}
|
|
530
|
+
]);
|
|
531
|
+
}
|
|
532
|
+
} catch (error) {
|
|
533
|
+
this.logger.error("Failed to record sign-in failure:", error);
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async recordFailedAttemptToDb(user, ip, now, recordUser = false) {
|
|
538
|
+
let geoLocation = {};
|
|
539
|
+
if (ip) {
|
|
540
|
+
geoLocation = this.getGeoLocation(ip);
|
|
541
|
+
}
|
|
542
|
+
const address = `${geoLocation.country || ""} ${geoLocation.region || ""} ${geoLocation.city || ""}`.trim();
|
|
543
|
+
return this.db.getRepository("signInFails").create({
|
|
544
|
+
values: {
|
|
545
|
+
userId: user.id,
|
|
546
|
+
status: recordUser,
|
|
547
|
+
ip,
|
|
548
|
+
address,
|
|
549
|
+
createdAt: now
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 获取最近的失败次数(从内存缓存中获取)
|
|
555
|
+
*/
|
|
556
|
+
getRecentFailureCount(userId) {
|
|
557
|
+
if (this.config.maxAttempts === 0) {
|
|
558
|
+
return 0;
|
|
559
|
+
}
|
|
560
|
+
const userRecords = this.failureRecords.get(userId) || [];
|
|
561
|
+
const windowStart = /* @__PURE__ */ new Date();
|
|
562
|
+
windowStart.setSeconds(windowStart.getSeconds() - this.config.windowSeconds);
|
|
563
|
+
return userRecords.filter((record) => record.createdAt > windowStart).length;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* 重置用户的失败记录
|
|
567
|
+
* @param username 用户名
|
|
568
|
+
*/
|
|
569
|
+
async resetFailedAttempts(userId) {
|
|
570
|
+
if (this.config.maxAttempts === 0) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
await this.db.getRepository("userLocks").destroy({
|
|
575
|
+
filter: {
|
|
576
|
+
userId
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
this.logger.info(`Reset sign-in failure records for user ${userId}`);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
this.logger.error(`Failed to reset sign-in failure records for user ${userId}:`, error);
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
_init = __decoratorStart(null);
|
|
587
|
+
__decorateElement(_init, 5, "db", _db_dec, PasswordPolicyService);
|
|
588
|
+
__decorateElement(_init, 5, "app", _app_dec, PasswordPolicyService);
|
|
589
|
+
__decorateElement(_init, 5, "logger", _logger_dec, PasswordPolicyService);
|
|
590
|
+
PasswordPolicyService = __decorateElement(_init, 0, "PasswordPolicyService", _PasswordPolicyService_decorators, PasswordPolicyService);
|
|
591
|
+
__runInitializers(_init, 1, PasswordPolicyService);
|
|
592
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
593
|
+
0 && (module.exports = {
|
|
594
|
+
PasswordPolicyService
|
|
595
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tachybase/plugin-password-policy",
|
|
3
3
|
"displayName": "Password policy",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.11",
|
|
5
5
|
"description": "Password policy, including password strength, password attempt limit, password lock time, ip whitelist/blacklist, etc.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"Security"
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"ipaddr.js": "^2.1.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@tachybase/
|
|
18
|
-
"@tachybase/
|
|
19
|
-
"@tachybase/
|
|
20
|
-
"@tachybase/schema": "1.2.
|
|
21
|
-
"@tachybase/
|
|
22
|
-
"@tachybase/
|
|
17
|
+
"@tachybase/actions": "1.2.11",
|
|
18
|
+
"@tachybase/client": "1.2.11",
|
|
19
|
+
"@tachybase/database": "1.2.11",
|
|
20
|
+
"@tachybase/schema": "1.2.11",
|
|
21
|
+
"@tachybase/utils": "1.2.11",
|
|
22
|
+
"@tachybase/server": "1.2.11"
|
|
23
23
|
},
|
|
24
24
|
"description.zh-CN": "密码策略, 包含密码强度、密码尝试次数限制、密码锁定时间、ip黑白名单等",
|
|
25
25
|
"displayName.zh-CN": "密码策略"
|