Compare commits

...
Author SHA1 Message Date
Abdullah Atta cdde244c69 s3: temporarily disable attachment uploads 2026-09-21 12:57:23 +05:00
Abdullah Atta 8b610e0952 cors: fix possible XSS vuln when embeding youtube-nocookie.com videos 2026-08-18 09:15:32 +05:00
Abdullah Atta 0a3ee07b95 api: add encryption verifier
encryption verifier is used for verifying the encryption key during password resets. It can be any encrypted item.
2026-08-18 08:36:02 +05:00
6 changed files with 83 additions and 32 deletions

No files matched your search

+31 -29
View File
@@ -45,28 +45,29 @@ namespace Notesnook.API.Controllers
[HttpPut]
public async Task<IActionResult> Upload([FromQuery] string name)
{
try
{
var userId = this.User.GetUserId();
return BadRequest(new { error = "Attachment storage is temporarily unavailable. Please try again later." });
// try
// {
// var userId = this.User.GetUserId();
var fileSize = HttpContext.Request.ContentLength ?? 0;
bool hasBody = fileSize > 0;
// var fileSize = HttpContext.Request.ContentLength ?? 0;
// bool hasBody = fileSize > 0;
if (!hasBody)
{
return Ok(Request.GetEncodedUrl() + "&access_token=" + Request.Headers.Authorization.ToString().Replace("Bearer ", ""));
}
// if (!hasBody)
// {
// return Ok(Request.GetEncodedUrl() + "&access_token=" + Request.Headers.Authorization.ToString().Replace("Bearer ", ""));
// }
if (Constants.IS_SELF_HOSTED) await UploadFileAsync(userId, name, fileSize);
else await UploadFileWithChecksAsync(userId, name, fileSize);
// if (Constants.IS_SELF_HOSTED) await UploadFileAsync(userId, name, fileSize);
// else await UploadFileWithChecksAsync(userId, name, fileSize);
return Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "Error uploading attachment for user.");
return BadRequest(new { error = "Failed to upload attachment." });
}
// return Ok();
// }
// catch (Exception ex)
// {
// logger.LogError(ex, "Error uploading attachment for user.");
// return BadRequest(new { error = "Failed to upload attachment." });
// }
}
private async Task UploadFileWithChecksAsync(string userId, string name, long fileSize)
@@ -115,17 +116,18 @@ namespace Notesnook.API.Controllers
[HttpGet("multipart")]
public async Task<IActionResult> MultipartUpload([FromQuery] string name, [FromQuery] int parts, [FromQuery] string? uploadId)
{
var userId = this.User.GetUserId();
try
{
var meta = await s3Service.StartMultipartUploadAsync(userId, name, parts, uploadId);
return Ok(meta);
}
catch (Exception ex)
{
logger.LogError(ex, "Error starting multipart upload for user.");
return BadRequest(new { error = "Failed to start multipart upload." });
}
return BadRequest(new { error = "Attachment storage is temporarily unavailable. Please try again later." });
// var userId = this.User.GetUserId();
// try
// {
// var meta = await s3Service.StartMultipartUploadAsync(userId, name, parts, uploadId);
// return Ok(meta);
// }
// catch (Exception ex)
// {
// logger.LogError(ex, "Error starting multipart upload for user.");
// return BadRequest(new { error = "Failed to start multipart upload." });
// }
}
[HttpDelete("multipart")]
@@ -127,6 +127,24 @@ namespace Notesnook.API.Controllers
}
}
[HttpGet("verifier")]
public async Task<IActionResult> GetEncryptionVerifier()
{
var userId = User.GetUserId();
try
{
var response = await UserService.GetEncryptionVerifier(userId);
if (response == null) return NotFound();
return Ok(response);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to get encryption verifier for user id: {UserId}", userId);
return BadRequest(new { error = ex.Message });
}
}
[HttpPost("reset")]
public async Task<IActionResult> Reset([FromForm] bool removeAttachments)
{
+1
View File
@@ -31,6 +31,7 @@ namespace Notesnook.API.Interfaces
Task DeleteUserAsync(string userId, string? jti, string password);
Task<bool> ResetUserAsync(string userId, bool removeAttachments);
Task<UserResponse> GetUserAsync(string userId);
Task<EncryptedData?> GetEncryptionVerifier(string userId);
Task SetUserKeysAsync(string userId, UserKeys keys);
}
}
@@ -36,6 +36,7 @@ using Streetwriters.Common;
using Streetwriters.Data.DbContexts;
using Streetwriters.Data.Interfaces;
using Streetwriters.Data.Repositories;
using AspNetCore.Identity.Mongo.Mongo;
namespace Notesnook.API.Repositories
{
+21
View File
@@ -30,6 +30,7 @@ using Notesnook.API.Helpers;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
using Notesnook.API.Models.Responses;
using Notesnook.API.Repositories;
using Streetwriters.Common;
using Streetwriters.Common.Accessors;
using Streetwriters.Common.Enums;
@@ -192,6 +193,26 @@ namespace Notesnook.API.Services
await Repositories.UsersSettings.UpdateAsync(userSettings.Id, userSettings);
}
public async Task<EncryptedData?> GetEncryptionVerifier(string userId)
{
SyncItemsRepository[] repositories = [Repositories.Notes, Repositories.Notebooks, Repositories.Shortcuts, Repositories.Contents, Repositories.Settings, Repositories.LegacySettings, Repositories.Attachments, Repositories.Reminders, Repositories.Relations, Repositories.Colors, Repositories.Tags, Repositories.Vaults, Repositories.InboxItemsHistory];
foreach (var repo in repositories)
{
var item = await repo.FindOneAsync((s) => s.UserId == userId && (s.KeyVersion == null || s.KeyVersion == 0));
if (item != null)
{
return new EncryptedData
{
Cipher = item.Cipher,
IV = item.IV,
Salt = "",
Length = item.Length
};
}
}
return null;
}
public async Task DeleteUserAsync(string userId)
{
logger.LogInformation("Deleting user {UserId}", userId);
+11 -3
View File
@@ -279,8 +279,8 @@ function serveYouTubeEmbed(url: string) {
</style>
</head>
<body>
<iframe src="${transformYouTubeUrl(
url,
<iframe src="${escapeHtmlAttr(
transformYouTubeUrl(url),
)}" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture;web-share" allowfullscreen referrerpolicy="strict-origin-when-cross-origin" title="Video player"></iframe>
</body>
</html>`;
@@ -299,6 +299,14 @@ function isYouTubeEmbed(urlString: string) {
);
}
function escapeHtmlAttr(str: string): string {
return str
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
// Transform YouTube URLs to use youtube-nocookie.com for enhanced privacy
function transformYouTubeUrl(urlString: string): string {
try {
@@ -315,7 +323,7 @@ function transformYouTubeUrl(urlString: string): string {
return url.toString();
}
return urlString;
return url.toString();
} catch {
return urlString;
}