mirror of
https://git.v0l.io/Kieran/void.cat.git
synced 2025-03-29 16:11:43 +01:00
OAuth (Google)
This commit is contained in:
parent
3e08056f0b
commit
1532d43189
@ -12,15 +12,17 @@ public class InfoController : Controller
|
||||
private readonly VoidSettings _settings;
|
||||
private readonly ITimeSeriesStatsReporter _timeSeriesStats;
|
||||
private readonly IEnumerable<string?> _fileStores;
|
||||
private readonly IEnumerable<string> _oAuthProviders;
|
||||
|
||||
public InfoController(IStatsReporter statsReporter, IFileMetadataStore fileMetadata, VoidSettings settings,
|
||||
ITimeSeriesStatsReporter stats, IEnumerable<IFileStore> fileStores)
|
||||
ITimeSeriesStatsReporter stats, IEnumerable<IFileStore> fileStores, IEnumerable<IOAuthProvider> oAuthProviders)
|
||||
{
|
||||
_statsReporter = statsReporter;
|
||||
_fileMetadata = fileMetadata;
|
||||
_settings = settings;
|
||||
_timeSeriesStats = stats;
|
||||
_fileStores = fileStores.Select(a => a.Key);
|
||||
_oAuthProviders = oAuthProviders.Select(a => a.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -43,7 +45,8 @@ public class InfoController : Controller
|
||||
CaptchaSiteKey = _settings.CaptchaSettings?.SiteKey,
|
||||
TimeSeriesMetrics = await _timeSeriesStats.GetBandwidth(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow),
|
||||
FileStores = _fileStores,
|
||||
UploadSegmentSize = _settings.UploadSegmentSize
|
||||
UploadSegmentSize = _settings.UploadSegmentSize,
|
||||
OAuthProviders = _oAuthProviders
|
||||
};
|
||||
}
|
||||
|
||||
@ -57,5 +60,6 @@ public class InfoController : Controller
|
||||
public IEnumerable<BandwidthPoint> TimeSeriesMetrics { get; init; }
|
||||
public IEnumerable<string?> FileStores { get; init; }
|
||||
public ulong? UploadSegmentSize { get; init; }
|
||||
public IEnumerable<string> OAuthProviders { get; init; }
|
||||
}
|
||||
}
|
@ -253,4 +253,7 @@ public static class Extensions
|
||||
|
||||
public static bool HasDiscord(this VoidSettings settings)
|
||||
=> settings.Discord != null;
|
||||
|
||||
public static bool HasGoogle(this VoidSettings settings)
|
||||
=> settings.Google != null;
|
||||
}
|
@ -104,7 +104,12 @@ namespace VoidCat.Model
|
||||
/// <summary>
|
||||
/// Discord application settings
|
||||
/// </summary>
|
||||
public DiscordSettings? Discord { get; init; }
|
||||
public OAuthDetails? Discord { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Google application settings
|
||||
/// </summary>
|
||||
public OAuthDetails? Google { get; init; }
|
||||
}
|
||||
|
||||
public sealed class TorSettings
|
||||
@ -180,7 +185,7 @@ namespace VoidCat.Model
|
||||
public string? Domain { get; init; }
|
||||
}
|
||||
|
||||
public sealed class DiscordSettings
|
||||
public sealed class OAuthDetails
|
||||
{
|
||||
public string? ClientId { get; init; }
|
||||
public string? ClientSecret { get; init; }
|
||||
|
@ -5,17 +5,14 @@ using VoidCat.Model.User;
|
||||
namespace VoidCat.Services.Users.Auth;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class DiscordOAuthProvider : GenericOAuth2Service<DiscordAccessToken>
|
||||
public class DiscordOAuthProvider : GenericOAuth2Service
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly DiscordSettings _discord;
|
||||
private readonly Uri _site;
|
||||
|
||||
public DiscordOAuthProvider(HttpClient client, VoidSettings settings) : base(client)
|
||||
public DiscordOAuthProvider(HttpClient client, VoidSettings settings) : base(client, settings)
|
||||
{
|
||||
_client = client;
|
||||
_discord = settings.Discord!;
|
||||
_site = settings.SiteUrl;
|
||||
Details = settings.Discord!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -48,71 +45,28 @@ public class DiscordOAuthProvider : GenericOAuth2Service<DiscordAccessToken>
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Dictionary<string, string> BuildAuthorizeQuery()
|
||||
=> new()
|
||||
{
|
||||
{"response_type", "code"},
|
||||
{"client_id", _discord.ClientId!},
|
||||
{"scope", "email identify"},
|
||||
{"prompt", "none"},
|
||||
{"redirect_uri", new Uri(_site, $"/auth/{Id}/token").ToString()}
|
||||
};
|
||||
|
||||
protected override Dictionary<string, string> BuildTokenQuery(string code)
|
||||
=> new()
|
||||
{
|
||||
{"client_id", _discord.ClientId!},
|
||||
{"client_secret", _discord.ClientSecret!},
|
||||
{"grant_type", "authorization_code"},
|
||||
{"code", code},
|
||||
{"redirect_uri", new Uri(_site, $"/auth/{Id}/token").ToString()}
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override UserAuthToken TransformDto(DiscordAccessToken dto)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Provider = Id,
|
||||
AccessToken = dto.AccessToken,
|
||||
Expires = DateTime.UtcNow.AddSeconds(dto.ExpiresIn),
|
||||
TokenType = dto.TokenType,
|
||||
RefreshToken = dto.RefreshToken,
|
||||
Scope = dto.Scope
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri AuthorizeEndpoint => new("https://discord.com/oauth2/authorize");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri TokenEndpoint => new("https://discord.com/api/oauth2/token");
|
||||
}
|
||||
|
||||
public class DiscordAccessToken
|
||||
{
|
||||
[JsonProperty("access_token")] public string AccessToken { get; init; }
|
||||
/// <inheritdoc />
|
||||
protected override OAuthDetails Details { get; }
|
||||
|
||||
[JsonProperty("expires_in")] public int ExpiresIn { get; init; }
|
||||
/// <inheritdoc />
|
||||
protected override string[] Scopes => new[] {"email", "identify"};
|
||||
|
||||
[JsonProperty("token_type")] public string TokenType { get; init; }
|
||||
internal class DiscordUser
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; init; } = null!;
|
||||
|
||||
[JsonProperty("refresh_token")] public string RefreshToken { get; init; }
|
||||
[JsonProperty("username")] public string Username { get; init; } = null!;
|
||||
|
||||
[JsonProperty("scope")] public string Scope { get; init; }
|
||||
}
|
||||
[JsonProperty("discriminator")] public string Discriminator { get; init; } = null!;
|
||||
|
||||
internal class DiscordUser
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; init; } = null!;
|
||||
[JsonProperty("avatar")] public string? Avatar { get; init; }
|
||||
|
||||
[JsonProperty("username")] public string Username { get; init; } = null!;
|
||||
|
||||
[JsonProperty("discriminator")] public string Discriminator { get; init; } = null!;
|
||||
|
||||
[JsonProperty("avatar")] public string? Avatar { get; init; }
|
||||
|
||||
[JsonProperty("email")] public string? Email { get; init; }
|
||||
[JsonProperty("email")] public string? Email { get; init; }
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Newtonsoft.Json;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Model.User;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
@ -7,12 +8,14 @@ namespace VoidCat.Services.Users.Auth;
|
||||
/// <summary>
|
||||
/// Generic base class for OAuth2 code grant flow
|
||||
/// </summary>
|
||||
public abstract class GenericOAuth2Service<TDto> : IOAuthProvider
|
||||
public abstract class GenericOAuth2Service : IOAuthProvider
|
||||
{
|
||||
private readonly Uri _uri;
|
||||
private readonly HttpClient _client;
|
||||
|
||||
protected GenericOAuth2Service(HttpClient client)
|
||||
protected GenericOAuth2Service(HttpClient client, VoidSettings settings)
|
||||
{
|
||||
_uri = settings.SiteUrl;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
@ -40,7 +43,8 @@ public abstract class GenericOAuth2Service<TDto> : IOAuthProvider
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to get token from provider: {Id}, response: {json}");
|
||||
}
|
||||
var dto = JsonConvert.DeserializeObject<TDto>(json);
|
||||
|
||||
var dto = JsonConvert.DeserializeObject<OAuthAccessToken>(json);
|
||||
return TransformDto(dto!);
|
||||
}
|
||||
|
||||
@ -51,20 +55,29 @@ public abstract class GenericOAuth2Service<TDto> : IOAuthProvider
|
||||
/// Build query args for authorize
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected abstract Dictionary<string, string> BuildAuthorizeQuery();
|
||||
protected virtual Dictionary<string, string> BuildAuthorizeQuery()
|
||||
=> new()
|
||||
{
|
||||
{"response_type", "code"},
|
||||
{"client_id", Details.ClientId!},
|
||||
{"scope", string.Join(" ", Scopes)},
|
||||
{"prompt", "none"},
|
||||
{"redirect_uri", new Uri(_uri, $"/auth/{Id}/token").ToString()}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Build query args for token generation
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected abstract Dictionary<string, string> BuildTokenQuery(string code);
|
||||
|
||||
/// <summary>
|
||||
/// Transform DTO to <see cref="UserAuthToken"/>
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
protected abstract UserAuthToken TransformDto(TDto dto);
|
||||
protected virtual Dictionary<string, string> BuildTokenQuery(string code)
|
||||
=> new()
|
||||
{
|
||||
{"client_id", Details.ClientId!},
|
||||
{"client_secret", Details.ClientSecret!},
|
||||
{"grant_type", "authorization_code"},
|
||||
{"code", code},
|
||||
{"redirect_uri", new Uri(_uri, $"/auth/{Id}/token").ToString()}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Authorize url for this service
|
||||
@ -75,4 +88,46 @@ public abstract class GenericOAuth2Service<TDto> : IOAuthProvider
|
||||
/// Generate token url for this service
|
||||
/// </summary>
|
||||
protected abstract Uri TokenEndpoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// OAuth client details
|
||||
/// </summary>
|
||||
protected abstract OAuthDetails Details { get; }
|
||||
|
||||
/// <summary>
|
||||
/// OAuth scopes
|
||||
/// </summary>
|
||||
protected abstract string[] Scopes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Transform DTO to <see cref="UserAuthToken"/>
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual UserAuthToken TransformDto(OAuthAccessToken dto)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Provider = Id,
|
||||
AccessToken = dto.AccessToken,
|
||||
Expires = DateTime.UtcNow.AddSeconds(dto.ExpiresIn),
|
||||
TokenType = dto.TokenType,
|
||||
RefreshToken = dto.RefreshToken,
|
||||
Scope = dto.Scope
|
||||
};
|
||||
}
|
||||
|
||||
protected class OAuthAccessToken
|
||||
{
|
||||
[JsonProperty("access_token")] public string AccessToken { get; init; }
|
||||
|
||||
[JsonProperty("expires_in")] public int ExpiresIn { get; init; }
|
||||
|
||||
[JsonProperty("token_type")] public string TokenType { get; init; }
|
||||
|
||||
[JsonProperty("refresh_token")] public string RefreshToken { get; init; }
|
||||
|
||||
[JsonProperty("scope")] public string Scope { get; init; }
|
||||
}
|
||||
}
|
51
VoidCat/Services/Users/Auth/GoogleOAuthProvider.cs
Normal file
51
VoidCat/Services/Users/Auth/GoogleOAuthProvider.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Model.User;
|
||||
|
||||
namespace VoidCat.Services.Users.Auth;
|
||||
|
||||
public class GoogleOAuthProvider : GenericOAuth2Service
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public GoogleOAuthProvider(HttpClient client, VoidSettings settings) : base(client, settings)
|
||||
{
|
||||
_client = client;
|
||||
Details = settings.Google!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Id => "google";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ValueTask<InternalUser?> GetUserDetails(UserAuthToken token)
|
||||
{
|
||||
var jwt = JwtPayload.Base64UrlDeserialize(token.AccessToken);
|
||||
return ValueTask.FromResult(new InternalUser()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Created = DateTimeOffset.UtcNow,
|
||||
LastLogin = DateTimeOffset.UtcNow,
|
||||
AuthType = AuthType.OAuth2,
|
||||
Email = jwt.Jti,
|
||||
DisplayName = jwt.Acr
|
||||
})!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri AuthorizeEndpoint => new("https://accounts.google.com/o/oauth2/v2/auth");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri TokenEndpoint => new("https://oauth2.googleapis.com/token");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override OAuthDetails Details { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string[] Scopes => new[]
|
||||
{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"};
|
||||
}
|
||||
|
||||
public sealed class GoogleUserAccount
|
||||
{
|
||||
}
|
@ -16,6 +16,11 @@ public static class UsersStartup
|
||||
services.AddTransient<IOAuthProvider, DiscordOAuthProvider>();
|
||||
}
|
||||
|
||||
if (settings.HasGoogle())
|
||||
{
|
||||
services.AddTransient<IOAuthProvider, GoogleOAuthProvider>();
|
||||
}
|
||||
|
||||
if (settings.HasPostgres())
|
||||
{
|
||||
services.AddTransient<IUserStore, PostgresUserStore>();
|
||||
|
@ -1,12 +1,13 @@
|
||||
import "./Admin.css";
|
||||
import {useSelector} from "react-redux";
|
||||
import {FileList} from "../FileList";
|
||||
import {UserList} from "./UserList";
|
||||
import {Navigate} from "react-router-dom";
|
||||
import {useApi} from "../Api";
|
||||
import {VoidButton} from "../VoidButton";
|
||||
import {useState} from "react";
|
||||
import VoidModal from "../VoidModal";
|
||||
import {useSelector} from "react-redux";
|
||||
import {Navigate} from "react-router-dom";
|
||||
|
||||
import {FileList} from "../Components/Shared/FileList";
|
||||
import {UserList} from "./UserList";
|
||||
import {useApi} from "../Components/Shared/Api";
|
||||
import {VoidButton} from "../Components/Shared/VoidButton";
|
||||
import VoidModal from "../Components/Shared/VoidModal";
|
||||
import EditUser from "./EditUser";
|
||||
|
||||
export function Admin() {
|
||||
@ -43,7 +44,7 @@ export function Admin() {
|
||||
</td>
|
||||
}}/>
|
||||
|
||||
{editUser !== null ?
|
||||
{editUser !== null ?
|
||||
<VoidModal title="Edit user">
|
||||
<EditUser user={editUser} onClose={() => setEditUser(null)}/>
|
||||
</VoidModal> : null}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {VoidButton} from "../VoidButton";
|
||||
import {useState} from "react";
|
||||
import {useSelector} from "react-redux";
|
||||
import {useApi} from "../Api";
|
||||
import {useApi} from "../Components/Shared/Api";
|
||||
import {VoidButton} from "../Components/Shared/VoidButton";
|
||||
|
||||
export default function EditUser(props) {
|
||||
const user = props.user;
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {useDispatch} from "react-redux";
|
||||
import {useEffect, useState} from "react";
|
||||
import {PagedSortBy, PageSortOrder} from "../Const";
|
||||
import {useApi} from "../Api";
|
||||
import {logout} from "../LoginState";
|
||||
import {PageSelector} from "../PageSelector";
|
||||
import moment from "moment";
|
||||
import {PagedSortBy, PageSortOrder} from "../Components/Shared/Const";
|
||||
import {useApi} from "../Components/Shared/Api";
|
||||
import {logout} from "../LoginState";
|
||||
import {PageSelector} from "../Components/Shared/PageSelector";
|
||||
|
||||
export function UserList(props) {
|
||||
const {AdminApi} = useApi();
|
||||
|
@ -1,12 +1,12 @@
|
||||
import {BrowserRouter, Routes, Route} from "react-router-dom";
|
||||
import {Provider} from "react-redux";
|
||||
import store from "./Store";
|
||||
import {FilePreview} from "./FilePreview";
|
||||
import {HomePage} from "./HomePage";
|
||||
import {FilePreview} from "./Pages/FilePreview";
|
||||
import {HomePage} from "./Pages/HomePage";
|
||||
import {Admin} from "./Admin/Admin";
|
||||
import {UserLogin} from "./UserLogin";
|
||||
import {Profile} from "./Profile";
|
||||
import {Header} from "./Header";
|
||||
import {UserLogin} from "./Pages/UserLogin";
|
||||
import {Profile} from "./Pages/Profile";
|
||||
import {Header} from "./Components/Shared/Header";
|
||||
|
||||
import './App.css';
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
import "./FileEdit.css";
|
||||
import {useState} from "react";
|
||||
import {useSelector} from "react-redux";
|
||||
import moment from "moment";
|
||||
|
||||
import {StrikePaymentConfig} from "./StrikePaymentConfig";
|
||||
import {NoPaymentConfig} from "./NoPaymentConfig";
|
||||
import {useApi} from "./Api";
|
||||
import "./FileEdit.css";
|
||||
import {useSelector} from "react-redux";
|
||||
import {VoidButton} from "./VoidButton";
|
||||
import moment from "moment";
|
||||
import {PaymentServices} from "./Const";
|
||||
import {useApi} from "../Shared/Api";
|
||||
import {VoidButton} from "../Shared/VoidButton";
|
||||
import {PaymentServices} from "../Shared/Const";
|
||||
|
||||
export function FileEdit(props) {
|
||||
const {Api} = useApi();
|
@ -1,6 +1,6 @@
|
||||
import FeatherIcon from "feather-icons-react";
|
||||
import {useState} from "react";
|
||||
import {VoidButton} from "./VoidButton";
|
||||
import {VoidButton} from "../Shared/VoidButton";
|
||||
|
||||
export function NoPaymentConfig(props) {
|
||||
const [saveStatus, setSaveStatus] = useState();
|
@ -1,7 +1,7 @@
|
||||
import {useState} from "react";
|
||||
import FeatherIcon from "feather-icons-react";
|
||||
import {PaymentCurrencies} from "./Const";
|
||||
import {VoidButton} from "./VoidButton";
|
||||
import {PaymentCurrencies} from "../Shared/Const";
|
||||
import {VoidButton} from "../Shared/VoidButton";
|
||||
|
||||
export function StrikePaymentConfig(props) {
|
||||
const file = props.file;
|
@ -1,10 +1,10 @@
|
||||
import "./FilePayment.css";
|
||||
import {FormatCurrency} from "./Util";
|
||||
import {PaymentServices} from "./Const";
|
||||
import {useState} from "react";
|
||||
import {FormatCurrency} from "../Shared/Util";
|
||||
import {PaymentServices} from "../Shared/Const";
|
||||
import {LightningPayment} from "./LightningPayment";
|
||||
import {useApi} from "./Api";
|
||||
import {VoidButton} from "./VoidButton";
|
||||
import {useApi} from "../Shared/Api";
|
||||
import {VoidButton} from "../Shared/VoidButton";
|
||||
|
||||
export function FilePayment(props) {
|
||||
const {Api} = useApi();
|
@ -1,9 +1,9 @@
|
||||
import QRCode from "qrcode.react";
|
||||
import {useEffect} from "react";
|
||||
|
||||
import {Countdown} from "./Countdown";
|
||||
import {PaymentOrderState} from "./Const";
|
||||
import {useApi} from "./Api";
|
||||
import {Countdown} from "../Shared/Countdown";
|
||||
import {PaymentOrderState} from "../Shared/Const";
|
||||
import {useApi} from "../Shared/Api";
|
||||
|
||||
export function LightningPayment(props) {
|
||||
const {Api} = useApi();
|
@ -1,8 +1,7 @@
|
||||
import "./Dropzone.css";
|
||||
import {Fragment, useEffect, useState} from "react";
|
||||
import {FileUpload} from "./FileUpload";
|
||||
|
||||
import "./Dropzone.css";
|
||||
|
||||
export function Dropzone(props) {
|
||||
let [files, setFiles] = useState([]);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import {ConstName, FormatBytes} from "./Util";
|
||||
import {RateCalculator} from "./RateCalculator";
|
||||
import * as CryptoJS from 'crypto-js';
|
||||
|
||||
import "./FileUpload.css";
|
||||
import {useEffect, useState} from "react";
|
||||
import * as CryptoJS from 'crypto-js';
|
||||
import {useSelector} from "react-redux";
|
||||
import {ApiHost} from "./Const";
|
||||
|
||||
import {ConstName, FormatBytes} from "../Shared/Util";
|
||||
import {RateCalculator} from "../Shared/RateCalculator";
|
||||
import {ApiHost} from "../Shared/Const";
|
||||
|
||||
const UploadState = {
|
||||
NotStarted: 0,
|
||||
@ -139,7 +139,7 @@ export function FileUpload(props) {
|
||||
setUState(UploadState.Hashing);
|
||||
let hash = await digest(props.file);
|
||||
calc.Reset();
|
||||
if(props.file.size >= uploadSize) {
|
||||
if (props.file.size >= uploadSize) {
|
||||
await doSplitXHRUpload(hash, uploadSize);
|
||||
} else {
|
||||
let xhr = await xhrSegment(props.file, hash);
|
||||
@ -162,7 +162,7 @@ export function FileUpload(props) {
|
||||
}
|
||||
handleXHRResult(xhr);
|
||||
}
|
||||
|
||||
|
||||
function handleXHRResult(xhr) {
|
||||
if (xhr.ok) {
|
||||
setUState(UploadState.Done);
|
||||
@ -173,7 +173,7 @@ export function FileUpload(props) {
|
||||
setResult(xhr.errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function digest(file) {
|
||||
const chunkSize = 100_000_000;
|
||||
let sha = CryptoJS.algo.SHA256.create();
|
@ -1,5 +1,5 @@
|
||||
import "./FooterLinks.css"
|
||||
import StrikeLogo from "./image/strike.png";
|
||||
import StrikeLogo from "../../image/strike.png";
|
||||
import {useSelector} from "react-redux";
|
||||
|
||||
export function FooterLinks() {
|
@ -1,8 +1,7 @@
|
||||
import {Fragment} from "react";
|
||||
import "./GlobalStats.css";
|
||||
import {Fragment} from "react";
|
||||
import FeatherIcon from "feather-icons-react";
|
||||
import {FormatBytes} from "./Util";
|
||||
|
||||
import "./GlobalStats.css";
|
||||
import {FormatBytes} from "../Shared/Util";
|
||||
import moment from "moment";
|
||||
import {useSelector} from "react-redux";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Bar, BarChart, Tooltip, XAxis} from "recharts";
|
||||
import {FormatBytes} from "./Util";
|
||||
import {FormatBytes} from "../Shared/Util";
|
||||
import moment from "moment";
|
||||
|
||||
export function MetricsGraph(props) {
|
@ -1,8 +1,9 @@
|
||||
import {useApi} from "./Api";
|
||||
import {useEffect, useState} from "react";
|
||||
import {VoidButton} from "./VoidButton";
|
||||
import moment from "moment";
|
||||
import VoidModal from "./VoidModal";
|
||||
|
||||
import {useApi} from "../Shared/Api";
|
||||
import {VoidButton} from "../Shared/VoidButton";
|
||||
import VoidModal from "../Shared/VoidModal";
|
||||
|
||||
export default function ApiKeyList() {
|
||||
const {Api} = useApi();
|
@ -1,10 +1,11 @@
|
||||
import moment from "moment";
|
||||
import {Link} from "react-router-dom";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {useEffect, useState} from "react";
|
||||
import {FormatBytes} from "./Util";
|
||||
import {logout} from "./LoginState";
|
||||
import {Link} from "react-router-dom";
|
||||
import moment from "moment";
|
||||
|
||||
import {PagedSortBy, PageSortOrder} from "./Const";
|
||||
import {logout} from "../../LoginState";
|
||||
import {FormatBytes} from "./Util";
|
||||
import {PageSelector} from "./PageSelector";
|
||||
|
||||
export function FileList(props) {
|
@ -3,9 +3,9 @@ import {Link} from "react-router-dom";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {InlineProfile} from "./InlineProfile";
|
||||
import {useApi} from "./Api";
|
||||
import {logout, setAuth, setProfile} from "./LoginState";
|
||||
import {logout, setAuth, setProfile} from "../../LoginState";
|
||||
import {useEffect} from "react";
|
||||
import {setInfo} from "./SiteInfoStore";
|
||||
import {setInfo} from "../../SiteInfoStore";
|
||||
|
||||
export function Header() {
|
||||
const dispatch = useDispatch();
|
@ -1,6 +1,6 @@
|
||||
import {useState} from "react";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {setAuth} from "./LoginState";
|
||||
import {setAuth} from "../../LoginState";
|
||||
import {useApi} from "./Api";
|
||||
import "./Login.css";
|
||||
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
||||
@ -13,6 +13,7 @@ export function Login() {
|
||||
const [error, setError] = useState();
|
||||
const [captchaResponse, setCaptchaResponse] = useState();
|
||||
const captchaKey = useSelector(state => state.info.info?.captchaSiteKey);
|
||||
const oAuthProviders = useSelector(state => state.info.info?.oAuthProviders);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
async function login(fnLogin) {
|
||||
@ -43,7 +44,10 @@ export function Login() {
|
||||
<VoidButton onClick={() => login(Api.login)}>Login</VoidButton>
|
||||
<VoidButton onClick={() => login(Api.register)}>Register</VoidButton>
|
||||
<br/>
|
||||
<VoidButton onClick={() => window.location.href = `/auth/discord`}>Login with Discord</VoidButton>
|
||||
{oAuthProviders ?
|
||||
oAuthProviders.map(a => <VoidButton key={a} onClick={() => window.location.href = `/auth/${a}`}>
|
||||
Login with {a}
|
||||
</VoidButton>) : null}
|
||||
{error ? <div className="error-msg">{error}</div> : null}
|
||||
</div>
|
||||
);
|
@ -1,15 +1,15 @@
|
||||
import "./FilePreview.css";
|
||||
import {Fragment, useEffect, useState} from "react";
|
||||
import {useParams} from "react-router-dom";
|
||||
import {TextPreview} from "./TextPreview";
|
||||
import {TextPreview} from "../Components/FilePreview/TextPreview";
|
||||
import FeatherIcon from "feather-icons-react";
|
||||
import "./FilePreview.css";
|
||||
import {FileEdit} from "./FileEdit";
|
||||
import {FilePayment} from "./FilePayment";
|
||||
import {useApi} from "./Api";
|
||||
import {FileEdit} from "../Components/FileEdit/FileEdit";
|
||||
import {FilePayment} from "../Components/FilePreview/FilePayment";
|
||||
import {useApi} from "../Components/Shared/Api";
|
||||
import {Helmet} from "react-helmet";
|
||||
import {FormatBytes} from "./Util";
|
||||
import {ApiHost} from "./Const";
|
||||
import {InlineProfile} from "./InlineProfile";
|
||||
import {FormatBytes} from "../Components/Shared/Util";
|
||||
import {ApiHost} from "../Components/Shared/Const";
|
||||
import {InlineProfile} from "../Components/Shared/InlineProfile";
|
||||
|
||||
export function FilePreview() {
|
||||
const {Api} = useApi();
|
@ -1,7 +1,7 @@
|
||||
import {Dropzone} from "./Dropzone";
|
||||
import {GlobalStats} from "./GlobalStats";
|
||||
import {FooterLinks} from "./FooterLinks";
|
||||
import {MetricsGraph} from "./MetricsGraph";
|
||||
import {Dropzone} from "../Components/FileUpload/Dropzone";
|
||||
import {GlobalStats} from "../Components/HomePage/GlobalStats";
|
||||
import {FooterLinks} from "../Components/HomePage/FooterLinks";
|
||||
import {MetricsGraph} from "../Components/HomePage/MetricsGraph";
|
||||
import {useSelector} from "react-redux";
|
||||
|
||||
export function HomePage() {
|
@ -1,16 +1,17 @@
|
||||
import {Fragment, useEffect, useState} from "react";
|
||||
import {useParams} from "react-router-dom";
|
||||
import {useApi} from "./Api";
|
||||
import {ApiHost, DefaultAvatar, UserFlags} from "./Const";
|
||||
import "./Profile.css";
|
||||
import {Fragment, useEffect, useState} from "react";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {logout, setProfile as setGlobalProfile} from "./LoginState";
|
||||
import {DigestAlgo} from "./FileUpload";
|
||||
import {buf2hex, hasFlag} from "./Util";
|
||||
import moment from "moment";
|
||||
import {FileList} from "./FileList";
|
||||
import {VoidButton} from "./VoidButton";
|
||||
import ApiKeyList from "./ApiKeyList";
|
||||
import {useParams} from "react-router-dom";
|
||||
|
||||
import {useApi} from "../Components/Shared/Api";
|
||||
import {ApiHost, DefaultAvatar, UserFlags} from "../Components/Shared/Const";
|
||||
import {logout, setProfile as setGlobalProfile} from "../LoginState";
|
||||
import {DigestAlgo} from "../Components/FileUpload/FileUpload";
|
||||
import {buf2hex, hasFlag} from "../Components/Shared/Util";
|
||||
import {FileList} from "../Components/Shared/FileList";
|
||||
import {VoidButton} from "../Components/Shared/VoidButton";
|
||||
import ApiKeyList from "../Components/Profile/ApiKeyList";
|
||||
|
||||
export function Profile() {
|
||||
const [profile, setProfile] = useState();
|
@ -1,4 +1,4 @@
|
||||
import {Login} from "./Login";
|
||||
import {Login} from "../Components/Shared/Login";
|
||||
import {useSelector} from "react-redux";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {useEffect} from "react";
|
Loading…
x
Reference in New Issue
Block a user