Compare commits
No commits in common. "de733a06824fdf0d15620c4e0a2b5609f245ef34" and "8b1d5b2c1b6a9afface067ada3b8d9b844b7ac0e" have entirely different histories.
de733a0682
...
8b1d5b2c1b
22 changed files with 212 additions and 618 deletions
|
@ -5,7 +5,6 @@ using Foxnouns.Backend.Database.Models;
|
||||||
using Foxnouns.Backend.Extensions;
|
using Foxnouns.Backend.Extensions;
|
||||||
using Foxnouns.Backend.Middleware;
|
using Foxnouns.Backend.Middleware;
|
||||||
using Foxnouns.Backend.Services;
|
using Foxnouns.Backend.Services;
|
||||||
using Foxnouns.Backend.Utils;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
@ -57,9 +56,8 @@ public class AuthController(
|
||||||
|
|
||||||
public record AddOauthAccountResponse(
|
public record AddOauthAccountResponse(
|
||||||
Snowflake Id,
|
Snowflake Id,
|
||||||
[property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] AuthType Type,
|
AuthType Type,
|
||||||
string RemoteId,
|
string RemoteId,
|
||||||
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
string? RemoteUsername
|
string? RemoteUsername
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,7 @@ public class MetaController : ApiControllerBase
|
||||||
new Limits(
|
new Limits(
|
||||||
MemberCount: MembersController.MaxMemberCount,
|
MemberCount: MembersController.MaxMemberCount,
|
||||||
BioLength: ValidationUtils.MaxBioLength,
|
BioLength: ValidationUtils.MaxBioLength,
|
||||||
CustomPreferences: ValidationUtils.MaxCustomPreferences,
|
CustomPreferences: ValidationUtils.MaxCustomPreferences
|
||||||
MaxAuthMethods: AuthUtils.MaxAuthMethodsPerType
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -50,10 +49,5 @@ public class MetaController : ApiControllerBase
|
||||||
private record UserInfo(int Total, int ActiveMonth, int ActiveWeek, int ActiveDay);
|
private record UserInfo(int Total, int ActiveMonth, int ActiveWeek, int ActiveDay);
|
||||||
|
|
||||||
// All limits that the frontend should know about (for UI purposes)
|
// All limits that the frontend should know about (for UI purposes)
|
||||||
private record Limits(
|
private record Limits(int MemberCount, int BioLength, int CustomPreferences);
|
||||||
int MemberCount,
|
|
||||||
int BioLength,
|
|
||||||
int CustomPreferences,
|
|
||||||
int MaxAuthMethods
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,22 +71,6 @@ public class DatabaseContext(DbContextOptions options) : DbContext(options)
|
||||||
modelBuilder.Entity<Member>().HasIndex(m => new { m.UserId, m.Name }).IsUnique();
|
modelBuilder.Entity<Member>().HasIndex(m => new { m.UserId, m.Name }).IsUnique();
|
||||||
modelBuilder.Entity<Member>().HasIndex(m => m.Sid).IsUnique();
|
modelBuilder.Entity<Member>().HasIndex(m => m.Sid).IsUnique();
|
||||||
modelBuilder.Entity<TemporaryKey>().HasIndex(k => k.Key).IsUnique();
|
modelBuilder.Entity<TemporaryKey>().HasIndex(k => k.Key).IsUnique();
|
||||||
modelBuilder
|
|
||||||
.Entity<AuthMethod>()
|
|
||||||
.HasIndex(m => new
|
|
||||||
{
|
|
||||||
m.AuthType,
|
|
||||||
m.RemoteId,
|
|
||||||
m.FediverseApplicationId,
|
|
||||||
})
|
|
||||||
.HasFilter("fediverse_application_id IS NOT NULL")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<AuthMethod>()
|
|
||||||
.HasIndex(m => new { m.AuthType, m.RemoteId })
|
|
||||||
.HasFilter("fediverse_application_id IS NULL")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
modelBuilder.Entity<User>().Property(u => u.Sid).HasDefaultValueSql("find_free_user_sid()");
|
modelBuilder.Entity<User>().Property(u => u.Sid).HasDefaultValueSql("find_free_user_sid()");
|
||||||
modelBuilder.Entity<User>().Property(u => u.Fields).HasColumnType("jsonb");
|
modelBuilder.Entity<User>().Property(u => u.Fields).HasColumnType("jsonb");
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Database.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
[DbContext(typeof(DatabaseContext))]
|
|
||||||
[Migration("20241128202508_AddAuthMethodUniqueIndex")]
|
|
||||||
public partial class AddAuthMethodUniqueIndex : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_auth_methods_auth_type_remote_id",
|
|
||||||
table: "auth_methods",
|
|
||||||
columns: new[] { "auth_type", "remote_id" },
|
|
||||||
unique: true,
|
|
||||||
filter: "fediverse_application_id IS NULL"
|
|
||||||
);
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_auth_methods_auth_type_remote_id_fediverse_application_id",
|
|
||||||
table: "auth_methods",
|
|
||||||
columns: new[] { "auth_type", "remote_id", "fediverse_application_id" },
|
|
||||||
unique: true,
|
|
||||||
filter: "fediverse_application_id IS NOT NULL"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "ix_auth_methods_auth_type_remote_id",
|
|
||||||
table: "auth_methods"
|
|
||||||
);
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "ix_auth_methods_auth_type_remote_id_fediverse_application_id",
|
|
||||||
table: "auth_methods"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -98,16 +98,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
b.HasIndex("UserId")
|
b.HasIndex("UserId")
|
||||||
.HasDatabaseName("ix_auth_methods_user_id");
|
.HasDatabaseName("ix_auth_methods_user_id");
|
||||||
|
|
||||||
b.HasIndex("AuthType", "RemoteId")
|
|
||||||
.IsUnique()
|
|
||||||
.HasDatabaseName("ix_auth_methods_auth_type_remote_id")
|
|
||||||
.HasFilter("fediverse_application_id IS NULL");
|
|
||||||
|
|
||||||
b.HasIndex("AuthType", "RemoteId", "FediverseApplicationId")
|
|
||||||
.IsUnique()
|
|
||||||
.HasDatabaseName("ix_auth_methods_auth_type_remote_id_fediverse_application_id")
|
|
||||||
.HasFilter("fediverse_application_id IS NOT NULL");
|
|
||||||
|
|
||||||
b.ToTable("auth_methods", (string)null);
|
b.ToTable("auth_methods", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -223,15 +223,6 @@ public class AuthService(
|
||||||
{
|
{
|
||||||
AssertValidAuthType(authType, null);
|
AssertValidAuthType(authType, null);
|
||||||
|
|
||||||
// This is already checked when
|
|
||||||
var currentCount = await db
|
|
||||||
.AuthMethods.Where(m => m.UserId == userId && m.AuthType == authType)
|
|
||||||
.CountAsync(ct);
|
|
||||||
if (currentCount >= AuthUtils.MaxAuthMethodsPerType)
|
|
||||||
throw new ApiError.BadRequest(
|
|
||||||
"Too many linked accounts of this type, maximum of 3 per account."
|
|
||||||
);
|
|
||||||
|
|
||||||
var authMethod = new AuthMethod
|
var authMethod = new AuthMethod
|
||||||
{
|
{
|
||||||
Id = snowflakeGenerator.GenerateSnowflake(),
|
Id = snowflakeGenerator.GenerateSnowflake(),
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { apiRequest } from "$api";
|
|
||||||
import ApiError, { ErrorCode, type RawApiError } from "$api/error";
|
|
||||||
import type { AuthResponse } from "$api/models/auth";
|
|
||||||
import { setToken } from "$lib";
|
|
||||||
import log from "$lib/log";
|
|
||||||
import { isRedirect, redirect, type RequestEvent } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
export default function createRegisterAction(callbackUrl: string) {
|
|
||||||
return async function ({ request, fetch, cookies }: RequestEvent) {
|
|
||||||
const data = await request.formData();
|
|
||||||
const username = data.get("username") as string | null;
|
|
||||||
const ticket = data.get("ticket") as string | null;
|
|
||||||
|
|
||||||
if (!username || !ticket)
|
|
||||||
return {
|
|
||||||
error: { message: "Bad request", code: ErrorCode.BadRequest, status: 403 } as RawApiError,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await apiRequest<AuthResponse>("POST", callbackUrl, {
|
|
||||||
body: { username, ticket },
|
|
||||||
isInternal: true,
|
|
||||||
fetch,
|
|
||||||
});
|
|
||||||
|
|
||||||
setToken(cookies, resp.token);
|
|
||||||
redirect(303, "/auth/welcome");
|
|
||||||
} catch (e) {
|
|
||||||
if (isRedirect(e)) throw e;
|
|
||||||
log.error("Could not sign up user with username %s:", username, e);
|
|
||||||
if (e instanceof ApiError) return { error: e.obj };
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { AuthType, User } from "./user";
|
import type { User } from "./user";
|
||||||
|
|
||||||
export type AuthResponse = {
|
export type AuthResponse = {
|
||||||
user: User;
|
user: User;
|
||||||
|
@ -21,10 +21,3 @@ export type AuthUrls = {
|
||||||
google?: string;
|
google?: string;
|
||||||
tumblr?: string;
|
tumblr?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AddAccountResponse = {
|
|
||||||
id: string;
|
|
||||||
type: AuthType;
|
|
||||||
remote_id: string;
|
|
||||||
remote_username?: string;
|
|
||||||
};
|
|
||||||
|
|
|
@ -16,5 +16,4 @@ export type Limits = {
|
||||||
member_count: number;
|
member_count: number;
|
||||||
bio_length: number;
|
bio_length: number;
|
||||||
custom_preferences: number;
|
custom_preferences: number;
|
||||||
max_auth_methods: number;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,11 +71,9 @@ export type PrideFlag = {
|
||||||
description: string | null;
|
description: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AuthType = "DISCORD" | "GOOGLE" | "TUMBLR" | "FEDIVERSE" | "EMAIL";
|
|
||||||
|
|
||||||
export type AuthMethod = {
|
export type AuthMethod = {
|
||||||
id: string;
|
id: string;
|
||||||
type: AuthType;
|
type: "DISCORD" | "GOOGLE" | "TUMBLR" | "FEDIVERSE" | "EMAIL";
|
||||||
remote_id: string;
|
remote_id: string;
|
||||||
remote_username?: string;
|
remote_username?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { AuthMethod } from "$api/models";
|
|
||||||
import AuthMethodRow from "./AuthMethodRow.svelte";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
methods: AuthMethod[];
|
|
||||||
canRemove: boolean;
|
|
||||||
max: number;
|
|
||||||
buttonLink: string;
|
|
||||||
buttonText: string;
|
|
||||||
};
|
|
||||||
let { methods, canRemove, max, buttonLink, buttonText }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if methods.length > 0}
|
|
||||||
<div class="list-group mb-3">
|
|
||||||
{#each methods as method (method.id)}
|
|
||||||
<AuthMethodRow {method} {canRemove} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if methods.length < max}
|
|
||||||
<a class="btn btn-primary mb-3" href={buttonLink}>{buttonText}</a>
|
|
||||||
{/if}
|
|
|
@ -1,26 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { t } from "$lib/i18n";
|
|
||||||
import type { AuthMethod } from "$api/models";
|
|
||||||
|
|
||||||
type Props = { method: AuthMethod; canRemove: boolean };
|
|
||||||
let { method, canRemove }: Props = $props();
|
|
||||||
|
|
||||||
let name = $derived(
|
|
||||||
method.type === "EMAIL" ? method.remote_id : (method.remote_username ?? method.remote_id),
|
|
||||||
);
|
|
||||||
let showId = $derived(method.type !== "FEDIVERSE");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
{name}
|
|
||||||
{#if showId}({method.remote_id}){/if}
|
|
||||||
</div>
|
|
||||||
{#if canRemove}
|
|
||||||
<div class="col text-end">
|
|
||||||
<a href="/settings/auth/remove-method/{method.id}">{$t("settings.auth-remove-method")}</a>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,34 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { AuthMethod, PartialUser } from "$api/models";
|
|
||||||
import { t } from "$lib/i18n";
|
|
||||||
|
|
||||||
type Props = { method: AuthMethod; user: PartialUser };
|
|
||||||
let { method, user }: Props = $props();
|
|
||||||
|
|
||||||
let name = $derived(
|
|
||||||
method.type === "EMAIL" ? method.remote_id : (method.remote_username ?? method.remote_id),
|
|
||||||
);
|
|
||||||
|
|
||||||
let text = $derived.by(() => {
|
|
||||||
switch (method.type) {
|
|
||||||
case "DISCORD":
|
|
||||||
return $t("auth.successful-link-discord");
|
|
||||||
case "GOOGLE":
|
|
||||||
return $t("auth.successful-link-google");
|
|
||||||
case "TUMBLR":
|
|
||||||
return $t("auth.successful-link-tumblr");
|
|
||||||
case "FEDIVERSE":
|
|
||||||
return $t("auth.successful-link-fedi");
|
|
||||||
default:
|
|
||||||
return "<you shouldn't see this!>";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>{$t("auth.new-auth-method-added")}</h1>
|
|
||||||
|
|
||||||
<p>{text} <code>{name}</code></p>
|
|
||||||
<p>{$t("auth.successful-link-profile-hint")}</p>
|
|
||||||
<p>
|
|
||||||
<a class="btn btn-primary" href="/@{user.username}">{$t("auth.successful-link-profile-link")}</a>
|
|
||||||
</p>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { RawApiError } from "$api/error";
|
|
||||||
import { enhance } from "$app/forms";
|
|
||||||
import ErrorAlert from "$components/ErrorAlert.svelte";
|
|
||||||
import { t } from "$lib/i18n";
|
|
||||||
import { Button, Input, Label } from "@sveltestrap/sveltestrap";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
title: string;
|
|
||||||
remoteLabel: string;
|
|
||||||
remoteUser: string;
|
|
||||||
ticket: string;
|
|
||||||
error?: RawApiError;
|
|
||||||
};
|
|
||||||
let { title, remoteLabel, remoteUser, ticket, error }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>{title}</h1>
|
|
||||||
|
|
||||||
{#if error}
|
|
||||||
<ErrorAlert {error} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<form method="POST" use:enhance>
|
|
||||||
<div class="mb-3">
|
|
||||||
<Label>{remoteLabel}</Label>
|
|
||||||
<Input type="text" readonly value={remoteUser} />
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<Label>{$t("auth.register-username-label")}</Label>
|
|
||||||
<Input type="text" name="username" required />
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="ticket" value={ticket} />
|
|
||||||
<Button color="primary" type="submit">{$t("auth.register-button")}</Button>
|
|
||||||
</form>
|
|
|
@ -18,8 +18,7 @@
|
||||||
"title": {
|
"title": {
|
||||||
"log-in": "Log in",
|
"log-in": "Log in",
|
||||||
"welcome": "Welcome",
|
"welcome": "Welcome",
|
||||||
"settings": "Settings",
|
"settings": "Settings"
|
||||||
"an-error-occurred": "An error occurred"
|
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"log-in-form-title": "Log in with email",
|
"log-in-form-title": "Log in with email",
|
||||||
|
@ -38,16 +37,7 @@
|
||||||
"register-button": "Register account",
|
"register-button": "Register account",
|
||||||
"register-with-mastodon": "Register with a Fediverse account",
|
"register-with-mastodon": "Register with a Fediverse account",
|
||||||
"log-in-with-fediverse-error-blurb": "Is your instance returning an error?",
|
"log-in-with-fediverse-error-blurb": "Is your instance returning an error?",
|
||||||
"log-in-with-fediverse-force-refresh-button": "Force a refresh on our end",
|
"log-in-with-fediverse-force-refresh-button": "Force a refresh on our end"
|
||||||
"register-with-discord": "Register with a Discord account",
|
|
||||||
"new-auth-method-added": "Successfully added authentication method!",
|
|
||||||
"successful-link-discord": "Your account has successfully been linked to the following Discord account:",
|
|
||||||
"successful-link-google": "Your account has successfully been linked to the following Google account:",
|
|
||||||
"successful-link-tumblr": "Your account has successfully been linked to the following Tumblr account:",
|
|
||||||
"successful-link-fedi": "Your account has successfully been linked to the following fediverse account:",
|
|
||||||
"successful-link-profile-hint": "You now can close this page, or go back to your profile:",
|
|
||||||
"successful-link-profile-link": "Go to your profile",
|
|
||||||
"remote-discord-account-label": "Your Discord account"
|
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"bad-request-header": "Something was wrong with your input",
|
"bad-request-header": "Something was wrong with your input",
|
||||||
|
@ -102,8 +92,7 @@
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
"username-update-success": "Successfully changed your username!",
|
"username-update-success": "Successfully changed your username!",
|
||||||
"create-member-title": "Create a new member",
|
"create-member-title": "Create a new member",
|
||||||
"create-member-name-label": "Member name",
|
"create-member-name-label": "Member name"
|
||||||
"auth-remove-method": "Remove"
|
|
||||||
},
|
},
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
import { apiRequest } from "$api";
|
|
||||||
import ApiError, { ErrorCode } from "$api/error";
|
|
||||||
import type { AddAccountResponse, CallbackResponse } from "$api/models/auth";
|
|
||||||
import { setToken } from "$lib";
|
|
||||||
import createRegisterAction from "$lib/actions/register.js";
|
|
||||||
import log from "$lib/log.js";
|
|
||||||
import { isRedirect, redirect } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
export const load = async ({ url, parent, fetch, cookies }) => {
|
|
||||||
const code = url.searchParams.get("code") as string | null;
|
|
||||||
const state = url.searchParams.get("state") as string | null;
|
|
||||||
if (!code || !state) throw new ApiError(undefined, ErrorCode.BadRequest).obj;
|
|
||||||
|
|
||||||
const { meUser } = await parent();
|
|
||||||
if (meUser) {
|
|
||||||
try {
|
|
||||||
const resp = await apiRequest<AddAccountResponse>(
|
|
||||||
"POST",
|
|
||||||
"/auth/discord/add-account/callback",
|
|
||||||
{
|
|
||||||
isInternal: true,
|
|
||||||
body: { code, state },
|
|
||||||
fetch,
|
|
||||||
cookies,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return { hasAccount: true, isLinkRequest: true, newAuthMethod: resp };
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof ApiError) return { isLinkRequest: true, error: e.obj };
|
|
||||||
log.error("error linking new discord account to user %s:", meUser.id, e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await apiRequest<CallbackResponse>("POST", "/auth/discord/callback", {
|
|
||||||
body: { code, state },
|
|
||||||
isInternal: true,
|
|
||||||
fetch,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.has_account) {
|
|
||||||
setToken(cookies, resp.token!);
|
|
||||||
redirect(303, `/@${resp.user!.username}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasAccount: false,
|
|
||||||
isLinkRequest: false,
|
|
||||||
ticket: resp.ticket!,
|
|
||||||
remoteUser: resp.remote_username!,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
if (isRedirect(e)) throw e;
|
|
||||||
if (e instanceof ApiError) return { isLinkRequest: false, error: e.obj };
|
|
||||||
log.error("error while requesting discord callback:", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const actions = {
|
|
||||||
default: createRegisterAction("/auth/discord/register"),
|
|
||||||
};
|
|
|
@ -1,31 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import Error from "$components/Error.svelte";
|
|
||||||
import NewAuthMethod from "$components/settings/NewAuthMethod.svelte";
|
|
||||||
import OauthRegistrationForm from "$components/settings/OauthRegistrationForm.svelte";
|
|
||||||
import { t } from "$lib/i18n";
|
|
||||||
import type { ActionData, PageData } from "./$types";
|
|
||||||
|
|
||||||
type Props = { data: PageData; form: ActionData };
|
|
||||||
let { data, form }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>{$t("auth.register-with-discord")} • pronouns.cc</title>
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
{#if data.error}
|
|
||||||
<h1>{$t("auth.register-with-discord")}</h1>
|
|
||||||
<Error error={data.error} />
|
|
||||||
{:else if data.isLinkRequest}
|
|
||||||
<NewAuthMethod method={data.newAuthMethod!} user={data.meUser!} />
|
|
||||||
{:else}
|
|
||||||
<OauthRegistrationForm
|
|
||||||
title={$t("auth.register-with-discord")}
|
|
||||||
remoteLabel={$t("auth.remote-discord-account-label")}
|
|
||||||
remoteUser={data.remoteUser!}
|
|
||||||
ticket={data.ticket!}
|
|
||||||
error={form?.error}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { apiRequest } from "$api";
|
import { apiRequest } from "$api";
|
||||||
import ApiError, { ErrorCode } from "$api/error";
|
import ApiError, { ErrorCode, type RawApiError } from "$api/error";
|
||||||
import type { CallbackResponse } from "$api/models/auth.js";
|
import type { AuthResponse, CallbackResponse } from "$api/models/auth.js";
|
||||||
import { setToken } from "$lib";
|
import { setToken } from "$lib";
|
||||||
import createRegisterAction from "$lib/actions/register.js";
|
import log from "$lib/log.js";
|
||||||
import { redirect } from "@sveltejs/kit";
|
import { isRedirect, redirect } from "@sveltejs/kit";
|
||||||
|
|
||||||
export const load = async ({ parent, params, url, fetch, cookies }) => {
|
export const load = async ({ parent, params, url, fetch, cookies }) => {
|
||||||
const { meUser } = await parent();
|
const { meUser } = await parent();
|
||||||
|
@ -33,5 +33,30 @@ export const load = async ({ parent, params, url, fetch, cookies }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
default: createRegisterAction("/auth/fediverse/register"),
|
default: async ({ request, fetch, cookies }) => {
|
||||||
|
const data = await request.formData();
|
||||||
|
const username = data.get("username") as string | null;
|
||||||
|
const ticket = data.get("ticket") as string | null;
|
||||||
|
|
||||||
|
if (!username || !ticket)
|
||||||
|
return {
|
||||||
|
error: { message: "Bad request", code: ErrorCode.BadRequest, status: 403 } as RawApiError,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await apiRequest<AuthResponse>("POST", "/auth/fediverse/register", {
|
||||||
|
body: { username, ticket },
|
||||||
|
isInternal: true,
|
||||||
|
fetch,
|
||||||
|
});
|
||||||
|
|
||||||
|
setToken(cookies, resp.token);
|
||||||
|
redirect(303, "/auth/welcome");
|
||||||
|
} catch (e) {
|
||||||
|
if (isRedirect(e)) throw e;
|
||||||
|
log.error("Could not sign up user with username %s:", username, e);
|
||||||
|
if (e instanceof ApiError) return { error: e.obj };
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { Button, Input, Label } from "@sveltestrap/sveltestrap";
|
||||||
import type { ActionData, PageData } from "./$types";
|
import type { ActionData, PageData } from "./$types";
|
||||||
import { t } from "$lib/i18n";
|
import { t } from "$lib/i18n";
|
||||||
import OauthRegistrationForm from "$components/settings/OauthRegistrationForm.svelte";
|
import { enhance } from "$app/forms";
|
||||||
|
import ErrorAlert from "$components/ErrorAlert.svelte";
|
||||||
|
|
||||||
type Props = { data: PageData; form: ActionData };
|
type Props = { data: PageData; form: ActionData };
|
||||||
let { data, form }: Props = $props();
|
let { data, form }: Props = $props();
|
||||||
|
@ -12,11 +14,22 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<OauthRegistrationForm
|
<h1>{$t("auth.register-with-mastodon")}</h1>
|
||||||
title={$t("auth.register-with-mastodon")}
|
|
||||||
remoteLabel={$t("auth.remote-fediverse-account-label")}
|
{#if form?.error}
|
||||||
remoteUser={data.remoteUser}
|
<ErrorAlert error={form?.error} />
|
||||||
ticket={data.ticket}
|
{/if}
|
||||||
error={form?.error}
|
|
||||||
/>
|
<form method="POST" use:enhance>
|
||||||
|
<div class="mb-3">
|
||||||
|
<Label>{$t("auth.remote-fediverse-account-label")}</Label>
|
||||||
|
<Input type="text" readonly value={data.remoteUser} />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<Label>{$t("auth.register-username-label")}</Label>
|
||||||
|
<Input type="text" name="username" required />
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="ticket" value={data.ticket} />
|
||||||
|
<Button color="primary" type="submit">{$t("auth.register-button")}</Button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { apiRequest } from "$api";
|
|
||||||
import type { AuthUrls } from "$api/models/auth";
|
|
||||||
|
|
||||||
export const load = async ({ fetch }) => {
|
|
||||||
const urls = await apiRequest<AuthUrls>("POST", "/auth/urls", { fetch, isInternal: true });
|
|
||||||
return { urls };
|
|
||||||
};
|
|
|
@ -1,65 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import AuthMethodList from "$components/settings/AuthMethodList.svelte";
|
|
||||||
import AuthMethodRow from "$components/settings/AuthMethodRow.svelte";
|
|
||||||
import type { PageData } from "./$types";
|
|
||||||
|
|
||||||
type Props = { data: PageData };
|
|
||||||
let { data }: Props = $props();
|
|
||||||
|
|
||||||
let max = $derived(data.meta.limits.max_auth_methods);
|
|
||||||
let canRemove = $derived(data.user.auth_methods.length > 1);
|
|
||||||
let emails = $derived(data.user.auth_methods.filter((m) => m.type === "EMAIL"));
|
|
||||||
let discordAccounts = $derived(data.user.auth_methods.filter((m) => m.type === "DISCORD"));
|
|
||||||
let googleAccounts = $derived(data.user.auth_methods.filter((m) => m.type === "GOOGLE"));
|
|
||||||
let tumblrAccounts = $derived(data.user.auth_methods.filter((m) => m.type === "TUMBLR"));
|
|
||||||
let fediAccounts = $derived(data.user.auth_methods.filter((m) => m.type === "FEDIVERSE"));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if data.urls.email_enabled}
|
|
||||||
<h3>Email addresses</h3>
|
|
||||||
<AuthMethodList
|
|
||||||
methods={emails}
|
|
||||||
{canRemove}
|
|
||||||
{max}
|
|
||||||
buttonLink="/settings/auth/add-email"
|
|
||||||
buttonText="Add email address"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if data.urls.discord}
|
|
||||||
<h3>Discord accounts</h3>
|
|
||||||
<AuthMethodList
|
|
||||||
methods={discordAccounts}
|
|
||||||
{canRemove}
|
|
||||||
{max}
|
|
||||||
buttonLink="/settings/auth/add-discord"
|
|
||||||
buttonText="Link Discord account"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if data.urls.google}
|
|
||||||
<h3>Google accounts</h3>
|
|
||||||
<AuthMethodList
|
|
||||||
methods={googleAccounts}
|
|
||||||
{canRemove}
|
|
||||||
{max}
|
|
||||||
buttonLink="/settings/auth/add-google"
|
|
||||||
buttonText="Link Google account"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if data.urls.tumblr}
|
|
||||||
<h3>Tumblr accounts</h3>
|
|
||||||
<AuthMethodList
|
|
||||||
methods={tumblrAccounts}
|
|
||||||
{canRemove}
|
|
||||||
{max}
|
|
||||||
buttonLink="/settings/auth/add-tumblr"
|
|
||||||
buttonText="Link Tumblr account"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<h3>Fediverse accounts</h3>
|
|
||||||
<AuthMethodList
|
|
||||||
methods={fediAccounts}
|
|
||||||
{canRemove}
|
|
||||||
{max}
|
|
||||||
buttonLink="/settings/auth/add-fediverse"
|
|
||||||
buttonText="Link Fediverse account"
|
|
||||||
/>
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { apiRequest } from "$api";
|
|
||||||
import { redirect } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
export const load = async ({ fetch, cookies }) => {
|
|
||||||
const { url } = await apiRequest<{ url: string }>("GET", "/auth/discord/add-account", {
|
|
||||||
isInternal: true,
|
|
||||||
fetch,
|
|
||||||
cookies,
|
|
||||||
});
|
|
||||||
|
|
||||||
redirect(303, url);
|
|
||||||
};
|
|
Loading…
Add table
Add a link
Reference in a new issue