Skip to content
This repository was archived by the owner on Jun 29, 2025. It is now read-only.

Commit 73730af

Browse files
author
Romain Ricard
committed
feat(share): add share ID length setting
- Add share ID length to share > settings - Use cryptographically secure RNG for IDs - Use secure default value for IDs length - Add FR and EN translation
1 parent 18d8cbb commit 73730af

File tree

5 files changed

+45
-13
lines changed

5 files changed

+45
-13
lines changed

backend/prisma/seed/config.seed.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ const configVariables: ConfigVariables = {
5151
defaultValue: "0",
5252
secret: false,
5353
},
54+
shareIdLength: {
55+
type: "number",
56+
defaultValue: "8",
57+
secret: false,
58+
},
5459
maxSize: {
5560
type: "number",
5661
defaultValue: "1000000000",

frontend/src/components/upload/modals/showCreateUploadModal.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const showCreateUploadModal = (
4040
allowUnauthenticatedShares: boolean;
4141
enableEmailRecepients: boolean;
4242
maxExpirationInHours: number;
43+
shareIdLength: number;
4344
simplified: boolean;
4445
},
4546
files: FileUpload[],
@@ -72,18 +73,28 @@ const showCreateUploadModal = (
7273
});
7374
};
7475

75-
const generateLink = () =>
76-
Buffer.from(Math.random().toString(), "utf8")
77-
.toString("base64")
78-
.substring(10, 17);
76+
const generateShareId = (length: number = 16) => {
77+
const chars =
78+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
79+
let result = "";
80+
const randomArray = new Uint8Array(length >= 3 ? length : 3);
81+
crypto.getRandomValues(randomArray);
82+
randomArray.forEach((number) => {
83+
result += chars[number % chars.length];
84+
});
85+
return result;
86+
};
7987

80-
const generateAvailableLink = async (times = 10): Promise<string> => {
88+
const generateAvailableLink = async (
89+
shareIdLength: number,
90+
times: number = 10,
91+
): Promise<string> => {
8192
if (times <= 0) {
8293
throw new Error("Could not generate available link");
8394
}
84-
const _link = generateLink();
95+
const _link = generateShareId(shareIdLength);
8596
if (!(await shareService.isShareIdAvailable(_link))) {
86-
return await generateAvailableLink(times - 1);
97+
return await generateAvailableLink(shareIdLength, times - 1);
8798
} else {
8899
return _link;
89100
}
@@ -102,12 +113,13 @@ const CreateUploadModalBody = ({
102113
allowUnauthenticatedShares: boolean;
103114
enableEmailRecepients: boolean;
104115
maxExpirationInHours: number;
116+
shareIdLength: number;
105117
};
106118
}) => {
107119
const modals = useModals();
108120
const t = useTranslate();
109121

110-
const generatedLink = generateLink();
122+
const generatedLink = generateShareId(options.shareIdLength);
111123

112124
const [showNotSignedInAlert, setShowNotSignedInAlert] = useState(true);
113125

@@ -229,7 +241,12 @@ const CreateUploadModalBody = ({
229241
<Button
230242
style={{ flex: "0 0 auto" }}
231243
variant="outline"
232-
onClick={() => form.setFieldValue("link", generateLink())}
244+
onClick={() =>
245+
form.setFieldValue(
246+
"link",
247+
generateShareId(options.shareIdLength),
248+
)
249+
}
233250
>
234251
<FormattedMessage id="common.button.generate" />
235252
</Button>
@@ -461,6 +478,7 @@ const SimplifiedCreateUploadModalModal = ({
461478
allowUnauthenticatedShares: boolean;
462479
enableEmailRecepients: boolean;
463480
maxExpirationInHours: number;
481+
shareIdLength: number;
464482
};
465483
}) => {
466484
const modals = useModals();
@@ -485,10 +503,12 @@ const SimplifiedCreateUploadModalModal = ({
485503
});
486504

487505
const onSubmit = form.onSubmit(async (values) => {
488-
const link = await generateAvailableLink().catch(() => {
489-
toast.error(t("upload.modal.link.error.taken"));
490-
return undefined;
491-
});
506+
const link = await generateAvailableLink(options.shareIdLength).catch(
507+
() => {
508+
toast.error(t("upload.modal.link.error.taken"));
509+
return undefined;
510+
},
511+
);
492512

493513
if (!link) {
494514
return;

frontend/src/i18n/translations/en-US.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,9 @@ export default {
468468
"admin.config.share.max-expiration": "Max expiration",
469469
"admin.config.share.max-expiration.description":
470470
"Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
471+
"admin.config.share.share-id-length": "Default share ID length",
472+
"admin.config.share.share-id-length.description":
473+
"Default length for the generated ID of a share. This value is also used to generate links for reverse shares. A value below 8 is not considered secure.",
471474
"admin.config.share.max-size": "Max size",
472475
"admin.config.share.max-size.description": "Maximum share size in bytes",
473476
"admin.config.share.zip-compression-level": "Zip compression level",

frontend/src/i18n/translations/fr-FR.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,9 @@ export default {
336336
"admin.config.share.allow-unauthenticated-shares.description": "Permet aux visiteurs de créer des partages",
337337
"admin.config.share.max-expiration": "Échéance",
338338
"admin.config.share.max-expiration.description": "Échéance du partage en heures. Indiquez 0 pour qu’il n’expire jamais.",
339+
"admin.config.share.share-id-length": "Taille de l'identifiant généré",
340+
"admin.config.share.share-id-length.description":
341+
"Taille par défaut de l'identifiant généré pour un partage. Cette valeur est aussi utilisée pour générer les liens des partages inverses. Une valeur inférieure à 8 n'est pas considérée sûre.",
339342
"admin.config.share.max-size": "Taille max",
340343
"admin.config.share.max-size.description": "Taille maximale du partage en octets",
341344
"admin.config.share.zip-compression-level": "Niveau de compression",

frontend/src/pages/upload/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ const Upload = ({
140140
),
141141
enableEmailRecepients: config.get("email.enableShareEmailRecipients"),
142142
maxExpirationInHours: config.get("share.maxExpiration"),
143+
shareIdLength: config.get("share.shareIdLength"),
143144
simplified,
144145
},
145146
files,

0 commit comments

Comments
 (0)