feat(share): add share ID length setting (#677)
- 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 Co-authored-by: Romain Ricard <romain.ricard@mines-ales.org>
This commit is contained in:
@@ -51,6 +51,11 @@ const configVariables: ConfigVariables = {
|
||||
defaultValue: "0",
|
||||
secret: false,
|
||||
},
|
||||
shareIdLength: {
|
||||
type: "number",
|
||||
defaultValue: "8",
|
||||
secret: false,
|
||||
},
|
||||
maxSize: {
|
||||
type: "number",
|
||||
defaultValue: "1000000000",
|
||||
|
||||
@@ -40,6 +40,7 @@ const showCreateUploadModal = (
|
||||
allowUnauthenticatedShares: boolean;
|
||||
enableEmailRecepients: boolean;
|
||||
maxExpirationInHours: number;
|
||||
shareIdLength: number;
|
||||
simplified: boolean;
|
||||
},
|
||||
files: FileUpload[],
|
||||
@@ -72,18 +73,28 @@ const showCreateUploadModal = (
|
||||
});
|
||||
};
|
||||
|
||||
const generateLink = () =>
|
||||
Buffer.from(Math.random().toString(), "utf8")
|
||||
.toString("base64")
|
||||
.substring(10, 17);
|
||||
const generateShareId = (length: number = 16) => {
|
||||
const chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
const randomArray = new Uint8Array(length >= 3 ? length : 3);
|
||||
crypto.getRandomValues(randomArray);
|
||||
randomArray.forEach((number) => {
|
||||
result += chars[number % chars.length];
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
const generateAvailableLink = async (times = 10): Promise<string> => {
|
||||
const generateAvailableLink = async (
|
||||
shareIdLength: number,
|
||||
times: number = 10,
|
||||
): Promise<string> => {
|
||||
if (times <= 0) {
|
||||
throw new Error("Could not generate available link");
|
||||
}
|
||||
const _link = generateLink();
|
||||
const _link = generateShareId(shareIdLength);
|
||||
if (!(await shareService.isShareIdAvailable(_link))) {
|
||||
return await generateAvailableLink(times - 1);
|
||||
return await generateAvailableLink(shareIdLength, times - 1);
|
||||
} else {
|
||||
return _link;
|
||||
}
|
||||
@@ -102,12 +113,13 @@ const CreateUploadModalBody = ({
|
||||
allowUnauthenticatedShares: boolean;
|
||||
enableEmailRecepients: boolean;
|
||||
maxExpirationInHours: number;
|
||||
shareIdLength: number;
|
||||
};
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
|
||||
const generatedLink = generateLink();
|
||||
const generatedLink = generateShareId(options.shareIdLength);
|
||||
|
||||
const [showNotSignedInAlert, setShowNotSignedInAlert] = useState(true);
|
||||
|
||||
@@ -229,7 +241,12 @@ const CreateUploadModalBody = ({
|
||||
<Button
|
||||
style={{ flex: "0 0 auto" }}
|
||||
variant="outline"
|
||||
onClick={() => form.setFieldValue("link", generateLink())}
|
||||
onClick={() =>
|
||||
form.setFieldValue(
|
||||
"link",
|
||||
generateShareId(options.shareIdLength),
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormattedMessage id="common.button.generate" />
|
||||
</Button>
|
||||
@@ -461,6 +478,7 @@ const SimplifiedCreateUploadModalModal = ({
|
||||
allowUnauthenticatedShares: boolean;
|
||||
enableEmailRecepients: boolean;
|
||||
maxExpirationInHours: number;
|
||||
shareIdLength: number;
|
||||
};
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
@@ -485,10 +503,12 @@ const SimplifiedCreateUploadModalModal = ({
|
||||
});
|
||||
|
||||
const onSubmit = form.onSubmit(async (values) => {
|
||||
const link = await generateAvailableLink().catch(() => {
|
||||
toast.error(t("upload.modal.link.error.taken"));
|
||||
return undefined;
|
||||
});
|
||||
const link = await generateAvailableLink(options.shareIdLength).catch(
|
||||
() => {
|
||||
toast.error(t("upload.modal.link.error.taken"));
|
||||
return undefined;
|
||||
},
|
||||
);
|
||||
|
||||
if (!link) {
|
||||
return;
|
||||
|
||||
@@ -468,6 +468,9 @@ export default {
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description":
|
||||
"Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.share-id-length": "Default share ID length",
|
||||
"admin.config.share.share-id-length.description":
|
||||
"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.",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
"admin.config.share.zip-compression-level": "Zip compression level",
|
||||
|
||||
@@ -336,6 +336,9 @@ export default {
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Permet aux visiteurs de créer des partages",
|
||||
"admin.config.share.max-expiration": "Échéance",
|
||||
"admin.config.share.max-expiration.description": "Échéance du partage en heures. Indiquez 0 pour qu’il n’expire jamais.",
|
||||
"admin.config.share.share-id-length": "Taille de l'identifiant généré",
|
||||
"admin.config.share.share-id-length.description":
|
||||
"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.",
|
||||
"admin.config.share.max-size": "Taille max",
|
||||
"admin.config.share.max-size.description": "Taille maximale du partage en octets",
|
||||
"admin.config.share.zip-compression-level": "Niveau de compression",
|
||||
|
||||
@@ -140,6 +140,7 @@ const Upload = ({
|
||||
),
|
||||
enableEmailRecepients: config.get("email.enableShareEmailRecipients"),
|
||||
maxExpirationInHours: config.get("share.maxExpiration"),
|
||||
shareIdLength: config.get("share.shareIdLength"),
|
||||
simplified,
|
||||
},
|
||||
files,
|
||||
|
||||
Reference in New Issue
Block a user