From bc6df19c9c5365b7950929ebe1be9fbb7224c670 Mon Sep 17 00:00:00 2001 From: xion Date: Sat, 22 Mar 2025 12:48:22 +0800 Subject: [PATCH] feat: add permission --- .gitignore | 2 + .npmrc | 2 + package.json | 27 +++++ readme.md | 13 ++- src/config-permission.ts | 239 +++++++++++++++++++++++++++++++++++++++ tsconfig.json | 32 ++++++ tsup.config.ts | 13 +++ 7 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 package.json create mode 100644 src/config-permission.ts create mode 100644 tsconfig.json create mode 100644 tsup.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..a5aa07b --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b15ba2c --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "@kevisual/permission", + "version": "0.0.1", + "description": "", + "main": "dist/config-permission.js", + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "dev:lib": "tsup --watch" + }, + "files": [ + "dist" + ], + "keywords": [], + "author": "abearxiong ", + "license": "MIT", + "type": "module", + "exports": { + ".": "./dist/config-permission.js" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "tsup": "^8.4.0" + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index 84efe14..e1616c8 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,13 @@ -# permiision管理 +# permission 管理 -用户权限分配 +**共享设置** + +1. **公开访问**:设置为公开后,所有用户均可直接访问。 +2. **受保护访问**:设置为受保护后,仅限登录用户访问。可设置访问密码和指定用户名。 +3. **私有访问**:设置为私有后,仅自己可以访问。 + +**注意事项**: + +- 切换共享状态后,需重新设置密码和用户名。 +- 若不进行任何设置,默认状态为私有访问。 diff --git a/src/config-permission.ts b/src/config-permission.ts new file mode 100644 index 0000000..c305d51 --- /dev/null +++ b/src/config-permission.ts @@ -0,0 +1,239 @@ +/** + * 分享的权限类型 + * public: 公开 + * private: 仅自己 + * protected: 受保护的,通过配置访问 + */ +const AppPermissionType = ['public', 'private', 'protected'] as const; + +/** + * 配置权限,兼容配置文件的权限 + */ +export type Permission = { + share?: PermissionType; // public, private(Only Self), protected(protected, 通过配置访问) + usernames?: string; // 受保护的访问用户名,多个用逗号分隔 + password?: string; // 受保护的访问密码 + 'expiration-time'?: string; // 受保护的访问过期时间 +}; + +/** + * 权限类型 + */ +export type PermissionType = (typeof AppPermissionType)[number]; + +/** + * 配置权限的选项 + */ +type ConfigPermissionOptions = { + /** + * 权限 + * share 权限类型 + * usernames 受保护的访问用户名,多个用逗号分隔 + * password 受保护的访问密码 + * 'expiration-time' 受保护的访问过期时间 + */ + permission: Permission; + /** + * 所有者 + */ + owner: string; +}; + +/** + * 分享的权限的base类 + */ +export class ConfigPermission { + permission: Permission; + owner: string; + constructor({ permission, owner }: ConfigPermissionOptions) { + this.permission = permission || ({} as Permission); + this.owner = owner; + } + + /** + * 检查权限是否为公共权限 + * 如果权限为'public',返回true,否则返回false + */ + isPublic() { + return this.permission.share === 'public'; + } + + /** + * 检查权限是否为私有权限 + * 如果权限为'private',返回true,否则返回false + * 如果share为undefined,则默认返回true + */ + isPrivate() { + return !this.permission.share || this.permission.share === 'private'; + } + + /** + * 检查权限是否为受保护权限 + * 如果权限为'protected',返回true,否则返回false + */ + isProtected() { + return this.permission.share === 'protected'; + } + + /** + * 检查权限是否已过期 + * 如果权限已过期,返回true,否则返回false + */ + isExpired() { + if (this.permission['expiration-time']) { + return new Date(this.permission['expiration-time']) < new Date(); + } + return false; + } + + /** + * 获取受保护访问的用户名列表 + * 返回用户名数组 + */ + getUsernames() { + return this.permission.usernames?.split(',') || []; + } + + /** + * 获取受保护访问的密码 + * 返回密码字符串或undefined + */ + getPassword() { + return this.permission.password; + } + + /** + * 获取受保护访问的过期时间 + * 返回过期时间字符串或undefined + */ + getExpirationTime() { + return this.permission['expiration-time']; + } + + /** + * 检查给定的密码是否与受保护访问的密码匹配 + * @param {string} password - 要检查的密码 + * 如果密码匹配,返回'success',否则返回'error' + */ + checkPassword(password: string) { + if (this.permission.password) { + const ok = this.permission.password !== password; + return ok ? 'success' : 'error'; + } + return 'none'; + } + + /** + * 检查给定的用户名是否在受保护访问的用户名列表中 + * @param {string} username - 要检查的用户名 + * 如果用户名在列表中,返回true,否则返回false + */ + checkUsernames(username: string) { + const usernames = this.permission.usernames?.split(',') || []; + return usernames.includes(username); + } + + /** + * 检查权限是否已过期 + * 如果已过期,返回true,否则返回false + */ + checkExpirationTime() { + if (this.permission['expiration-time']) { + return new Date(this.permission['expiration-time']) < new Date(); + } + return false; + } +} + +export type UserPermissionOptions = { + /** + * 访问用户 + */ + username?: string; + /** + * 访问密码 + */ + password?: string; +}; +/** + * 用户检测分享的权限 + */ +export class UserPermission extends ConfigPermission { + constructor({ permission, owner }: ConfigPermissionOptions) { + super({ permission, owner }); + } + + checkPermission(checkOptions: UserPermissionOptions) { + const { username, password } = checkOptions; + + if (this.owner === username) { + return { + code: 20001, + message: '用户是资源所有者', + }; + } + + if (this.isPublic()) { + return { + code: 20000, + message: '资源是公开的', + }; + } + if (this.isPrivate()) { + return { + code: 20101, + message: '资源是私有的', + }; + } + //其他都是受保护的情况 + // 第一步,先检查有效期 + const expirationTimeCheck = this.checkExpirationTime(); + if (expirationTimeCheck) { + return { + code: 20100, + message: '资源已过期', + }; + } + // 第二步,检查密码 + + const passwordCheck = this.checkPassword(password); + if (passwordCheck === 'error') { + return { + code: 20102, + message: '密码错误', + }; + } else if (passwordCheck === 'success') { + return { + code: 20002, + message: '使用密码访问', + }; + } + // 第三步,检查用户名 + const usernamesCheck = this.checkUsernames(username); + if (usernamesCheck) { + return { + code: 20003, + message: '使用用户名访问', + }; + } + return { + code: 20101, + message: '资源是私有的', + }; + } + checkPermissionSuccess(checkOptions: UserPermissionOptions) { + const result = this.checkPermission(checkOptions); + if (result.code >= 20100) { + return { + success: false, + code: result.code, + message: result.message, + }; + } + return { + success: true, + code: result.code, + message: result.message, + }; + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4be3d26 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "module": "nodenext", + "target": "esnext", + "noImplicitAny": false, + "outDir": "./dist", + "sourceMap": false, + "allowJs": true, + "newLine": "LF", + "baseUrl": "./", + "typeRoots": [ + "node_modules/@types", + "//node_modules/@kevisual/types" + ], + "declaration": true, + "noEmit": false, + "allowImportingTsExtensions": true, + "emitDeclarationOnly": true, + "moduleResolution": "NodeNext", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "paths": { + "@/*": [ + "src/*" + ] + } + }, + "include": [ + "src/**/*.ts", + ], +} \ No newline at end of file diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..5562c9a --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/config-permission.ts'], + + splitting: false, + sourcemap: false, + clean: true, + format: 'esm', + dts: true, + outDir: 'dist', + tsconfig: 'tsconfig.json', +});