feat: change permission for mino -resources
This commit is contained in:
@@ -36,3 +36,26 @@ export const checkAuth = async (req: http.IncomingMessage, res: http.ServerRespo
|
||||
}
|
||||
return { tokenUser, token };
|
||||
};
|
||||
|
||||
export const getLoginUser = async (req: http.IncomingMessage) => {
|
||||
let token = (req.headers?.['authorization'] as string) || (req.headers?.['Authorization'] as string) || '';
|
||||
const url = new URL(req.url || '', 'http://localhost');
|
||||
if (!token) {
|
||||
token = url.searchParams.get('token') || '';
|
||||
}
|
||||
if (!token) {
|
||||
const parsedCookies = cookie.parse(req.headers.cookie || '');
|
||||
token = parsedCookies.token || '';
|
||||
}
|
||||
|
||||
if (token) {
|
||||
token = token.replace('Bearer ', '');
|
||||
}
|
||||
let tokenUser;
|
||||
try {
|
||||
tokenUser = await User.verifyToken(token);
|
||||
return { tokenUser, token };
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/**
|
||||
* 更新时间:2025-03-17
|
||||
* 第二次更新:2025-03-22
|
||||
*/
|
||||
import { minioClient } from '@/app.ts';
|
||||
import { IncomingMessage, ServerResponse } from 'http';
|
||||
import { bucketName } from '@/modules/minio.ts';
|
||||
import { checkAuth } from '../middleware/auth.ts';
|
||||
import { getLoginUser } from '../middleware/auth.ts';
|
||||
import { BucketItemStat } from 'minio';
|
||||
import { UserPermission, Permission } from '@kevisual/permission';
|
||||
|
||||
/**
|
||||
* 过滤 metaData 中的 key, 去除 password, accesskey, secretkey,
|
||||
@@ -23,54 +25,7 @@ const filterKeys = (metaData: Record<string, string>, clearKeys: string[] = [])
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
};
|
||||
export const checkMetaAuth = async (
|
||||
metaData: Record<string, string>,
|
||||
{ tokenUser, token, share, userKey, password }: { tokenUser: any; share: ShareType; token: string; userKey: string; password: string },
|
||||
) => {
|
||||
const tokenUsername = tokenUser?.username;
|
||||
if (share === 'public') {
|
||||
return {
|
||||
code: 20000,
|
||||
msg: '资源是公开的',
|
||||
};
|
||||
}
|
||||
if (tokenUsername === userKey) {
|
||||
return {
|
||||
code: 20001,
|
||||
msg: '用户是资源所有者',
|
||||
};
|
||||
}
|
||||
// 1. 检查资源是否过期(有,则检查)
|
||||
if (metaData['expiration-time']) {
|
||||
const expirationTime = new Date(metaData['expiration-time']);
|
||||
const currentTime = new Date();
|
||||
if (expirationTime < currentTime) {
|
||||
return {
|
||||
code: 20100,
|
||||
msg: '资源已过期',
|
||||
};
|
||||
}
|
||||
}
|
||||
// 2. 检查密码是否正确(可选,password存在的情况)
|
||||
if (password && metaData.password && password === metaData.password) {
|
||||
return {
|
||||
code: 20002,
|
||||
msg: '用户通过密码正确访问',
|
||||
};
|
||||
}
|
||||
const usernames = metaData['usernames'] || '';
|
||||
if (usernames && usernames.includes(tokenUsername)) {
|
||||
// TODO: 可以检查用户的orgs 是否在 metaData['orgs'] 中
|
||||
return {
|
||||
code: 20003,
|
||||
msg: '用户在usernames列表中',
|
||||
};
|
||||
}
|
||||
return {
|
||||
code: 20101,
|
||||
msg: '用户没有权限访问',
|
||||
};
|
||||
};
|
||||
|
||||
export const NotFoundFile = (res: ServerResponse, msg?: string, code = 404) => {
|
||||
res.writeHead(code, { 'Content-Type': 'text/plain' });
|
||||
res.end(msg || 'Not Found File');
|
||||
@@ -95,22 +50,21 @@ export const authMinio = async (req: IncomingMessage, res: ServerResponse, objec
|
||||
if (stat.size === 0) {
|
||||
return NotFoundFile(res);
|
||||
}
|
||||
const share = (metaData.share as ShareType) || 'private'; // 默认是 private
|
||||
let tokenUser: any = null;
|
||||
let token: string | null = null;
|
||||
if (password && metaData.password && password === metaData.password) {
|
||||
// 密码正确,直接返回
|
||||
} else if (share !== 'public') {
|
||||
({ tokenUser, token } = await checkAuth(req, res));
|
||||
if (!tokenUser) {
|
||||
return;
|
||||
}
|
||||
const checkMetaAuthResult = await checkMetaAuth(metaData, { tokenUser, token, share, userKey, password });
|
||||
const { code } = checkMetaAuthResult;
|
||||
if (code >= 20100) {
|
||||
return NotFoundFile(res);
|
||||
}
|
||||
const { tokenUser } = await getLoginUser(req);
|
||||
const username = tokenUser?.username;
|
||||
const owner = userKey;
|
||||
const permission = new UserPermission({
|
||||
permission: metaData as Permission,
|
||||
owner,
|
||||
});
|
||||
const checkPermissionResult = permission.checkPermissionSuccess({
|
||||
username,
|
||||
password,
|
||||
});
|
||||
if (!checkPermissionResult.success) {
|
||||
return NotFoundFile(res, checkPermissionResult.message, checkPermissionResult.code);
|
||||
}
|
||||
|
||||
const contentLength = stat.size;
|
||||
const etag = stat.etag;
|
||||
const lastModified = stat.lastModified.toISOString();
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useContextKey } from '@kevisual/use-config/context';
|
||||
import { sequelize } from '../../../modules/sequelize.ts';
|
||||
import { DataTypes, Model } from 'sequelize';
|
||||
import { Permission } from '@kevisual/permission';
|
||||
|
||||
export interface ConfigData {
|
||||
key?: string;
|
||||
version?: string;
|
||||
permission?: {
|
||||
share?: 'public' | 'private';
|
||||
};
|
||||
permission?: Permission;
|
||||
}
|
||||
|
||||
export type Config = Partial<InstanceType<typeof ConfigModel>>;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ConfigModel } from '../models/model.ts';
|
||||
import { ConfigModel, Config } from '../models/model.ts';
|
||||
import { CustomError } from '@kevisual/router';
|
||||
import { redis } from '@/app.ts';
|
||||
import { User } from '@/models/user.ts';
|
||||
import { UserPermission, UserPermissionOptions } from '@kevisual/permission';
|
||||
|
||||
export class ShareConfigService extends ConfigModel {
|
||||
/**
|
||||
* 获取分享的配置
|
||||
@@ -9,10 +11,23 @@ export class ShareConfigService extends ConfigModel {
|
||||
* @param username 分享者的username
|
||||
* @returns 配置
|
||||
*/
|
||||
static async getShareConfig(key: string, username: string) {
|
||||
const shareCacheConfig = await redis.get(`config:share:${username}:${key}`);
|
||||
static async getShareConfig(key: string, username: string, options: UserPermissionOptions) {
|
||||
const shareCacheConfigString = await redis.get(`config:share:${username}:${key}`);
|
||||
let shareCacheConfig: Config;
|
||||
try {
|
||||
shareCacheConfig = JSON.parse(shareCacheConfigString);
|
||||
} catch (e) {
|
||||
await redis.set(`config:share:${username}:${key}`, '', 'EX', 0); // 删除缓存
|
||||
throw new CustomError(400, 'config parse error');
|
||||
}
|
||||
const owner = username;
|
||||
if (shareCacheConfig) {
|
||||
return JSON.parse(shareCacheConfig);
|
||||
const permission = new UserPermission({ permission: shareCacheConfig?.data?.permission, owner });
|
||||
const result = permission.checkPermissionSuccess(options);
|
||||
if (!result.success) {
|
||||
throw new CustomError(403, 'no permission');
|
||||
}
|
||||
return shareCacheConfig;
|
||||
}
|
||||
const user = await User.findOne({
|
||||
where: { username },
|
||||
@@ -26,8 +41,9 @@ export class ShareConfigService extends ConfigModel {
|
||||
if (!config) {
|
||||
throw new CustomError(404, 'config not found');
|
||||
}
|
||||
const configData = config?.data?.permission;
|
||||
if (configData?.share !== 'public') {
|
||||
const permission = new UserPermission({ permission: config?.data?.permission, owner });
|
||||
const result = permission.checkPermissionSuccess(options);
|
||||
if (!result.success) {
|
||||
throw new CustomError(403, 'no permission');
|
||||
}
|
||||
await redis.set(`config:share:${username}:${key}`, JSON.stringify(config), 'EX', 60 * 60 * 24 * 7); // 7天
|
||||
@@ -35,7 +51,7 @@ export class ShareConfigService extends ConfigModel {
|
||||
}
|
||||
static async expireShareConfig(key: string, username: string) {
|
||||
if (key && username) {
|
||||
await redis.del(`config:share:${username}:${key}`);
|
||||
await redis.set(`config:share:${username}:${key}`, '', 'EX', 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,22 @@ app
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const { key, username } = ctx.query?.data || {};
|
||||
if (!key) {
|
||||
ctx.throw(400, 'key is required');
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const { p, username, configKey } = ctx.query || {};
|
||||
const queryUsername = tokenUser?.username;
|
||||
const password = p;
|
||||
if (!configKey) {
|
||||
ctx.throw(400, 'configKey is required');
|
||||
}
|
||||
if (!username) {
|
||||
ctx.throw(400, 'username is required');
|
||||
}
|
||||
|
||||
try {
|
||||
const config = await ShareConfigService.getShareConfig(key, username);
|
||||
const config = await ShareConfigService.getShareConfig(configKey, username, {
|
||||
username: queryUsername,
|
||||
password,
|
||||
});
|
||||
ctx.body = config;
|
||||
} catch (error) {
|
||||
if (error?.code === 500) {
|
||||
|
||||
Reference in New Issue
Block a user