mirror of
https://github.com/samsonjs/immich.git
synced 2026-03-25 09:15:56 +00:00
feat: verify permissions (#25647)
This commit is contained in:
parent
b06c21325e
commit
81c93101a0
4 changed files with 48 additions and 8 deletions
|
|
@ -4,6 +4,7 @@ import {
|
||||||
AssetBulkUploadCheckResult,
|
AssetBulkUploadCheckResult,
|
||||||
AssetMediaResponseDto,
|
AssetMediaResponseDto,
|
||||||
AssetMediaStatus,
|
AssetMediaStatus,
|
||||||
|
Permission,
|
||||||
addAssetsToAlbum,
|
addAssetsToAlbum,
|
||||||
checkBulkUpload,
|
checkBulkUpload,
|
||||||
createAlbum,
|
createAlbum,
|
||||||
|
|
@ -20,13 +21,11 @@ import { Stats, createReadStream } from 'node:fs';
|
||||||
import { stat, unlink } from 'node:fs/promises';
|
import { stat, unlink } from 'node:fs/promises';
|
||||||
import path, { basename } from 'node:path';
|
import path, { basename } from 'node:path';
|
||||||
import { Queue } from 'src/queue';
|
import { Queue } from 'src/queue';
|
||||||
import { BaseOptions, Batcher, authenticate, crawl, sha1 } from 'src/utils';
|
import { BaseOptions, Batcher, authenticate, crawl, requirePermissions, s, sha1 } from 'src/utils';
|
||||||
|
|
||||||
const UPLOAD_WATCH_BATCH_SIZE = 100;
|
const UPLOAD_WATCH_BATCH_SIZE = 100;
|
||||||
const UPLOAD_WATCH_DEBOUNCE_TIME_MS = 10_000;
|
const UPLOAD_WATCH_DEBOUNCE_TIME_MS = 10_000;
|
||||||
|
|
||||||
const s = (count: number) => (count === 1 ? '' : 's');
|
|
||||||
|
|
||||||
// TODO figure out why `id` is missing
|
// TODO figure out why `id` is missing
|
||||||
type AssetBulkUploadCheckResults = Array<AssetBulkUploadCheckResult & { id: string }>;
|
type AssetBulkUploadCheckResults = Array<AssetBulkUploadCheckResult & { id: string }>;
|
||||||
type Asset = { id: string; filepath: string };
|
type Asset = { id: string; filepath: string };
|
||||||
|
|
@ -136,6 +135,7 @@ export const startWatch = async (
|
||||||
|
|
||||||
export const upload = async (paths: string[], baseOptions: BaseOptions, options: UploadOptionsDto) => {
|
export const upload = async (paths: string[], baseOptions: BaseOptions, options: UploadOptionsDto) => {
|
||||||
await authenticate(baseOptions);
|
await authenticate(baseOptions);
|
||||||
|
await requirePermissions([Permission.AssetUpload]);
|
||||||
|
|
||||||
const scanFiles = await scan(paths, options);
|
const scanFiles = await scan(paths, options);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
import { getMyUser } from '@immich/sdk';
|
import { getMyUser, Permission } from '@immich/sdk';
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import { mkdir, unlink } from 'node:fs/promises';
|
import { mkdir, unlink } from 'node:fs/promises';
|
||||||
import { BaseOptions, connect, getAuthFilePath, logError, withError, writeAuthFile } from 'src/utils';
|
import {
|
||||||
|
BaseOptions,
|
||||||
|
connect,
|
||||||
|
getAuthFilePath,
|
||||||
|
logError,
|
||||||
|
requirePermissions,
|
||||||
|
withError,
|
||||||
|
writeAuthFile,
|
||||||
|
} from 'src/utils';
|
||||||
|
|
||||||
export const login = async (url: string, key: string, options: BaseOptions) => {
|
export const login = async (url: string, key: string, options: BaseOptions) => {
|
||||||
console.log(`Logging in to ${url}`);
|
console.log(`Logging in to ${url}`);
|
||||||
|
|
@ -9,6 +17,7 @@ export const login = async (url: string, key: string, options: BaseOptions) => {
|
||||||
const { configDirectory: configDir } = options;
|
const { configDirectory: configDir } = options;
|
||||||
|
|
||||||
await connect(url, key);
|
await connect(url, key);
|
||||||
|
await requirePermissions([Permission.UserRead]);
|
||||||
|
|
||||||
const [error, user] = await withError(getMyUser());
|
const [error, user] = await withError(getMyUser());
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { getAssetStatistics, getMyUser, getServerVersion, getSupportedMediaTypes } from '@immich/sdk';
|
import { getAssetStatistics, getMyUser, getServerVersion, getSupportedMediaTypes, Permission } from '@immich/sdk';
|
||||||
import { BaseOptions, authenticate } from 'src/utils';
|
import { authenticate, BaseOptions, requirePermissions } from 'src/utils';
|
||||||
|
|
||||||
export const serverInfo = async (options: BaseOptions) => {
|
export const serverInfo = async (options: BaseOptions) => {
|
||||||
const { url } = await authenticate(options);
|
const { url } = await authenticate(options);
|
||||||
|
await requirePermissions([Permission.ServerAbout, Permission.AssetStatistics, Permission.UserRead]);
|
||||||
|
|
||||||
const [versionInfo, mediaTypes, stats, userInfo] = await Promise.all([
|
const [versionInfo, mediaTypes, stats, userInfo] = await Promise.all([
|
||||||
getServerVersion(),
|
getServerVersion(),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { getMyUser, init, isHttpError } from '@immich/sdk';
|
import { ApiKeyResponseDto, getMyApiKey, getMyUser, init, isHttpError, Permission } from '@immich/sdk';
|
||||||
import { convertPathToPattern, glob } from 'fast-glob';
|
import { convertPathToPattern, glob } from 'fast-glob';
|
||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import { createReadStream } from 'node:fs';
|
import { createReadStream } from 'node:fs';
|
||||||
|
|
@ -34,6 +34,36 @@ export const authenticate = async (options: BaseOptions): Promise<AuthDto> => {
|
||||||
return auth;
|
return auth;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const s = (count: number) => (count === 1 ? '' : 's');
|
||||||
|
|
||||||
|
let _apiKey: ApiKeyResponseDto;
|
||||||
|
export const requirePermissions = async (permissions: Permission[]) => {
|
||||||
|
if (!_apiKey) {
|
||||||
|
_apiKey = await getMyApiKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_apiKey.permissions.includes(Permission.All)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const missing: Permission[] = [];
|
||||||
|
|
||||||
|
for (const permission of permissions) {
|
||||||
|
if (!_apiKey.permissions.includes(permission)) {
|
||||||
|
missing.push(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missing.length > 0) {
|
||||||
|
const combined = missing.map((permission) => `"${permission}"`).join(', ');
|
||||||
|
console.log(
|
||||||
|
`Missing required permission${s(missing.length)}: ${combined}.
|
||||||
|
Please make sure your API key has the correct permissions.`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const connect = async (url: string, key: string) => {
|
export const connect = async (url: string, key: string) => {
|
||||||
const wellKnownUrl = new URL('.well-known/immich', url);
|
const wellKnownUrl = new URL('.well-known/immich', url);
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue