126 lines
3.7 KiB
C#
126 lines
3.7 KiB
C#
using System.Text.Json;
|
|
|
|
namespace Notemark.Services;
|
|
|
|
public class ShareService : IShareService
|
|
{
|
|
private readonly string _dataPath;
|
|
private readonly JsonSerializerOptions _jsonOptions;
|
|
|
|
public ShareService(IWebHostEnvironment environment)
|
|
{
|
|
var baseDataPath = Environment.GetEnvironmentVariable("NOTEMARK_DATA_DIR");
|
|
if (string.IsNullOrWhiteSpace(baseDataPath))
|
|
{
|
|
baseDataPath = Path.Combine(environment.ContentRootPath, "data");
|
|
}
|
|
|
|
_dataPath = Path.Combine(baseDataPath, "shares");
|
|
try
|
|
{
|
|
Directory.CreateDirectory(_dataPath);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"[ShareService Init Error] Failed to create {_dataPath}: {ex.Message}");
|
|
}
|
|
|
|
_jsonOptions = new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
WriteIndented = true
|
|
};
|
|
}
|
|
|
|
public async Task<string> CreateShareTokenAsync(string sessionId, string mode)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sessionId))
|
|
throw new ArgumentException("Session ID cannot be empty", nameof(sessionId));
|
|
|
|
if (mode != "readonly" && mode != "editable")
|
|
throw new ArgumentException("Mode must be 'readonly' or 'editable'", nameof(mode));
|
|
|
|
var token = GenerateToken();
|
|
var shareToken = new ShareToken(
|
|
SessionId: sessionId,
|
|
Mode: mode,
|
|
CreatedAt: DateTime.UtcNow
|
|
);
|
|
|
|
var filePath = GetTokenFilePath(token);
|
|
var dir = Path.GetDirectoryName(filePath);
|
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
}
|
|
|
|
var json = JsonSerializer.Serialize(shareToken, _jsonOptions);
|
|
await File.WriteAllTextAsync(filePath, json, System.Text.Encoding.UTF8);
|
|
|
|
return token;
|
|
}
|
|
|
|
public async Task<ShareToken?> GetShareTokenAsync(string token)
|
|
{
|
|
var filePath = GetTokenFilePath(token);
|
|
|
|
if (!File.Exists(filePath))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
var json = await File.ReadAllTextAsync(filePath);
|
|
return JsonSerializer.Deserialize<ShareToken>(json, _jsonOptions);
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task RevokeShareTokenAsync(string token)
|
|
{
|
|
var shareToken = await GetShareTokenAsync(token);
|
|
if (shareToken == null)
|
|
return;
|
|
|
|
var revokedToken = shareToken with { RevokedAt = DateTime.UtcNow };
|
|
var filePath = GetTokenFilePath(token);
|
|
var json = JsonSerializer.Serialize(revokedToken, _jsonOptions);
|
|
await File.WriteAllTextAsync(filePath, json);
|
|
}
|
|
|
|
public async Task<bool> IsValidTokenAsync(string token)
|
|
{
|
|
var shareToken = await GetShareTokenAsync(token);
|
|
|
|
if (shareToken == null)
|
|
return false;
|
|
|
|
// Check if revoked
|
|
if (shareToken.RevokedAt.HasValue)
|
|
return false;
|
|
|
|
// Check if expired
|
|
if (shareToken.ExpiresAt.HasValue && shareToken.ExpiresAt.Value < DateTime.UtcNow)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private string GenerateToken()
|
|
{
|
|
// Generate a URL-safe random token
|
|
return Convert.ToBase64String(Guid.NewGuid().ToByteArray())
|
|
.Replace('+', '-')
|
|
.Replace('/', '_')
|
|
.Replace("=", "");
|
|
}
|
|
|
|
private string GetTokenFilePath(string token)
|
|
{
|
|
// Sanitize token for filename
|
|
var sanitizedToken = string.Concat(token.Where(c => char.IsLetterOrDigit(c) || c == '-' || c == '_'));
|
|
return Path.Combine(_dataPath, $"{sanitizedToken}.json");
|
|
}
|
|
} |