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:
Romain Ricard
2024-11-24 17:25:50 +01:00
committed by GitHub
parent f78ffd69e7
commit 9d4bb55a09
5 changed files with 45 additions and 13 deletions

View File

@@ -51,6 +51,11 @@ const configVariables: ConfigVariables = {
defaultValue: "0",
secret: false,
},
shareIdLength: {
type: "number",
defaultValue: "8",
secret: false,
},
maxSize: {
type: "number",
defaultValue: "1000000000",

View File

@@ -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;

View File

@@ -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",

View File

@@ -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 quil nexpire 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",

View File

@@ -140,6 +140,7 @@ const Upload = ({
),
enableEmailRecepients: config.get("email.enableShareEmailRecipients"),
maxExpirationInHours: config.get("share.maxExpiration"),
shareIdLength: config.get("share.shareIdLength"),
simplified,
},
files,