feat: allow linking fediverse account to existing user
This commit is contained in:
		
							parent
							
								
									d6bb2f7743
								
							
						
					
					
						commit
						97191933cb
					
				
					 14 changed files with 306 additions and 93 deletions
				
			
		|  | @ -31,6 +31,7 @@ type User struct { | |||
| 	Fediverse         *string | ||||
| 	FediverseUsername *string | ||||
| 	FediverseAppID    *int64 | ||||
| 	FediverseInstance *string | ||||
| 
 | ||||
| 	MaxInvites int | ||||
| 
 | ||||
|  | @ -99,7 +100,8 @@ func (db *DB) CreateUser(ctx context.Context, tx pgx.Tx, username string) (u Use | |||
| } | ||||
| 
 | ||||
| func (db *DB) FediverseUser(ctx context.Context, userID string, instanceAppID int64) (u User, err error) { | ||||
| 	sql, args, err := sq.Select("*").From("users"). | ||||
| 	sql, args, err := sq.Select("*", "(SELECT instance FROM fediverse_apps WHERE id = users.fediverse_app_id) AS fediverse_instance"). | ||||
| 		From("users"). | ||||
| 		Where("fediverse = ?", userID).Where("fediverse_app_id = ?", instanceAppID). | ||||
| 		ToSql() | ||||
| 	if err != nil { | ||||
|  | @ -141,7 +143,8 @@ func (u *User) UpdateFromFedi(ctx context.Context, ex Execer, userID, username s | |||
| 
 | ||||
| // DiscordUser fetches a user by Discord user ID. | ||||
| func (db *DB) DiscordUser(ctx context.Context, discordID string) (u User, err error) { | ||||
| 	sql, args, err := sq.Select("*").From("users").Where("discord = ?", discordID).ToSql() | ||||
| 	sql, args, err := sq.Select("*", "(SELECT instance FROM fediverse_apps WHERE id = users.fediverse_app_id) AS fediverse_instance"). | ||||
| 		From("users").Where("discord = ?", discordID).ToSql() | ||||
| 	if err != nil { | ||||
| 		return u, errors.Wrap(err, "building sql") | ||||
| 	} | ||||
|  | @ -181,7 +184,8 @@ func (u *User) UpdateFromDiscord(ctx context.Context, ex Execer, du *discordgo.U | |||
| 
 | ||||
| // User gets a user by ID. | ||||
| func (db *DB) User(ctx context.Context, id xid.ID) (u User, err error) { | ||||
| 	sql, args, err := sq.Select("*").From("users").Where("id = ?", id).ToSql() | ||||
| 	sql, args, err := sq.Select("*", "(SELECT instance FROM fediverse_apps WHERE id = users.fediverse_app_id) AS fediverse_instance"). | ||||
| 		From("users").Where("id = ?", id).ToSql() | ||||
| 	if err != nil { | ||||
| 		return u, errors.Wrap(err, "building sql") | ||||
| 	} | ||||
|  |  | |||
|  | @ -203,15 +203,15 @@ func (s *Server) discordSignup(w http.ResponseWriter, r *http.Request) error { | |||
| 
 | ||||
| 	u, err := s.DB.CreateUser(ctx, tx, req.Username) | ||||
| 	if err != nil { | ||||
| 		if errors.Cause(err) == db.ErrUsernameTaken { | ||||
| 			return server.APIError{Code: server.ErrUsernameTaken} | ||||
| 		} | ||||
| 
 | ||||
| 		return errors.Wrap(err, "creating user") | ||||
| 	} | ||||
| 
 | ||||
| 	err = u.UpdateFromDiscord(ctx, tx, du) | ||||
| 	if err != nil { | ||||
| 		if errors.Cause(err) == db.ErrUsernameTaken { | ||||
| 			return server.APIError{Code: server.ErrUsernameTaken} | ||||
| 		} | ||||
| 
 | ||||
| 		return errors.Wrap(err, "updating user from discord") | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -174,6 +174,57 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type fediLinkRequest struct { | ||||
| 	Instance string `json:"instance"` | ||||
| 	Ticket   string `json:"ticket"` | ||||
| } | ||||
| 
 | ||||
| func (s *Server) mastodonLink(w http.ResponseWriter, r *http.Request) error { | ||||
| 	ctx := r.Context() | ||||
| 
 | ||||
| 	claims, _ := server.ClaimsFromContext(ctx) | ||||
| 
 | ||||
| 	req, err := Decode[fediLinkRequest](r) | ||||
| 	if err != nil { | ||||
| 		return server.APIError{Code: server.ErrBadRequest} | ||||
| 	} | ||||
| 
 | ||||
| 	app, err := s.DB.FediverseApp(ctx, req.Instance) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "getting instance application") | ||||
| 	} | ||||
| 
 | ||||
| 	u, err := s.DB.User(ctx, claims.UserID) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "getting user") | ||||
| 	} | ||||
| 
 | ||||
| 	if u.Fediverse != nil { | ||||
| 		return server.APIError{Code: server.ErrAlreadyLinked} | ||||
| 	} | ||||
| 
 | ||||
| 	mu := new(partialMastodonAccount) | ||||
| 	err = s.DB.GetJSON(ctx, "mastodon:"+req.Ticket, &mu) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("getting mastoAPI user for ticket: %v", err) | ||||
| 
 | ||||
| 		return server.APIError{Code: server.ErrInvalidTicket} | ||||
| 	} | ||||
| 
 | ||||
| 	err = u.UpdateFromFedi(ctx, s.DB, mu.ID, mu.Username, app.ID) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "updating user from mastoAPI") | ||||
| 	} | ||||
| 
 | ||||
| 	fields, err := s.DB.UserFields(ctx, u.ID) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "getting user fields") | ||||
| 	} | ||||
| 
 | ||||
| 	render.JSON(w, r, dbUserToUserResponse(u, fields)) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type fediSignupRequest struct { | ||||
| 	Instance   string `json:"instance"` | ||||
| 	Ticket     string `json:"ticket"` | ||||
|  | @ -225,15 +276,15 @@ func (s *Server) mastodonSignup(w http.ResponseWriter, r *http.Request) error { | |||
| 
 | ||||
| 	u, err := s.DB.CreateUser(ctx, tx, req.Username) | ||||
| 	if err != nil { | ||||
| 		if errors.Cause(err) == db.ErrUsernameTaken { | ||||
| 			return server.APIError{Code: server.ErrUsernameTaken} | ||||
| 		} | ||||
| 
 | ||||
| 		return errors.Wrap(err, "creating user") | ||||
| 	} | ||||
| 
 | ||||
| 	err = u.UpdateFromFedi(ctx, tx, mu.ID, mu.Username, app.ID) | ||||
| 	if err != nil { | ||||
| 		if errors.Cause(err) == db.ErrUsernameTaken { | ||||
| 			return server.APIError{Code: server.ErrUsernameTaken} | ||||
| 		} | ||||
| 
 | ||||
| 		return errors.Wrap(err, "updating user from mastoAPI") | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -33,21 +33,28 @@ type userResponse struct { | |||
| 
 | ||||
| 	Discord         *string `json:"discord"` | ||||
| 	DiscordUsername *string `json:"discord_username"` | ||||
| 
 | ||||
| 	Fediverse         *string `json:"fediverse"` | ||||
| 	FediverseUsername *string `json:"fediverse_username"` | ||||
| 	FediverseInstance *string `json:"fediverse_instance"` | ||||
| } | ||||
| 
 | ||||
| func dbUserToUserResponse(u db.User, fields []db.Field) *userResponse { | ||||
| 	return &userResponse{ | ||||
| 		ID:              u.ID, | ||||
| 		Username:        u.Username, | ||||
| 		DisplayName:     u.DisplayName, | ||||
| 		Bio:             u.Bio, | ||||
| 		Avatar:          u.Avatar, | ||||
| 		Links:           db.NotNull(u.Links), | ||||
| 		Names:           db.NotNull(u.Names), | ||||
| 		Pronouns:        db.NotNull(u.Pronouns), | ||||
| 		Fields:          db.NotNull(fields), | ||||
| 		Discord:         u.Discord, | ||||
| 		DiscordUsername: u.DiscordUsername, | ||||
| 		ID:                u.ID, | ||||
| 		Username:          u.Username, | ||||
| 		DisplayName:       u.DisplayName, | ||||
| 		Bio:               u.Bio, | ||||
| 		Avatar:            u.Avatar, | ||||
| 		Links:             db.NotNull(u.Links), | ||||
| 		Names:             db.NotNull(u.Names), | ||||
| 		Pronouns:          db.NotNull(u.Pronouns), | ||||
| 		Fields:            db.NotNull(fields), | ||||
| 		Discord:           u.Discord, | ||||
| 		DiscordUsername:   u.DiscordUsername, | ||||
| 		Fediverse:         u.Fediverse, | ||||
| 		FediverseUsername: u.FediverseUsername, | ||||
| 		FediverseInstance: u.FediverseInstance, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -78,7 +85,7 @@ func Mount(srv *server.Server, r chi.Router) { | |||
| 		r.Route("/mastodon", func(r chi.Router) { | ||||
| 			r.Post("/callback", server.WrapHandler(s.mastodonCallback)) | ||||
| 			r.Post("/signup", server.WrapHandler(s.mastodonSignup)) | ||||
| 			r.With(server.MustAuth).Post("/add-provider", server.WrapHandler(nil)) | ||||
| 			r.With(server.MustAuth).Post("/add-provider", server.WrapHandler(s.mastodonLink)) | ||||
| 		}) | ||||
| 
 | ||||
| 		// invite routes | ||||
|  |  | |||
|  | @ -159,15 +159,6 @@ func (s *Server) getMeUser(w http.ResponseWriter, r *http.Request) error { | |||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// get fedi instance name if the user has a linked fedi account | ||||
| 	var fediInstance *string | ||||
| 	if u.FediverseAppID != nil { | ||||
| 		app, err := s.DB.FediverseAppByID(ctx, *u.FediverseAppID) | ||||
| 		if err == nil { | ||||
| 			fediInstance = &app.Instance | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	render.JSON(w, r, GetMeResponse{ | ||||
| 		GetUserResponse:   dbUserToResponse(u, fields, members), | ||||
| 		MaxInvites:        u.MaxInvites, | ||||
|  | @ -175,7 +166,7 @@ func (s *Server) getMeUser(w http.ResponseWriter, r *http.Request) error { | |||
| 		DiscordUsername:   u.DiscordUsername, | ||||
| 		Fediverse:         u.Fediverse, | ||||
| 		FediverseUsername: u.FediverseUsername, | ||||
| 		FediverseInstance: fediInstance, | ||||
| 		FediverseInstance: u.FediverseInstance, | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -94,6 +94,7 @@ const ( | |||
| 	ErrDeletionPending     = 1011 // own user deletion pending, returned with undo code | ||||
| 	ErrRecentExport        = 1012 // latest export is too recent | ||||
| 	ErrUnsupportedInstance = 1013 // unsupported fediverse software | ||||
| 	ErrAlreadyLinked       = 1014 // user already has linked account of the same type | ||||
| 
 | ||||
| 	// User-related error codes | ||||
| 	ErrUserNotFound = 2001 | ||||
|  | @ -130,6 +131,7 @@ var errCodeMessages = map[int]string{ | |||
| 	ErrDeletionPending:     "Your account is pending deletion", | ||||
| 	ErrRecentExport:        "Your latest data export is less than 1 day old", | ||||
| 	ErrUnsupportedInstance: "Unsupported instance software", | ||||
| 	ErrAlreadyLinked:       "Your account is already linked to an account of this type", | ||||
| 
 | ||||
| 	ErrUserNotFound: "User not found", | ||||
| 
 | ||||
|  | @ -163,6 +165,7 @@ var errCodeStatuses = map[int]int{ | |||
| 	ErrDeletionPending:     http.StatusBadRequest, | ||||
| 	ErrRecentExport:        http.StatusBadRequest, | ||||
| 	ErrUnsupportedInstance: http.StatusBadRequest, | ||||
| 	ErrAlreadyLinked:       http.StatusBadRequest, | ||||
| 
 | ||||
| 	ErrUserNotFound: http.StatusNotFound, | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,6 +21,9 @@ export interface MeUser extends User { | |||
|   max_invites: number; | ||||
|   discord: string | null; | ||||
|   discord_username: string | null; | ||||
|   fediverse: string | null; | ||||
|   fediverse_username: string | null; | ||||
|   fediverse_instance: string | null; | ||||
| } | ||||
| 
 | ||||
| export interface Field { | ||||
|  |  | |||
|  | @ -4,10 +4,11 @@ | |||
| 
 | ||||
|   import { goto } from "$app/navigation"; | ||||
|   import type { APIError, MeUser } from "$lib/api/entities"; | ||||
|   import { apiFetch } from "$lib/api/fetch"; | ||||
|   import { apiFetch, apiFetchClient } from "$lib/api/fetch"; | ||||
|   import { userStore } from "$lib/store"; | ||||
|   import type { PageData } from "./$types"; | ||||
|   import ErrorAlert from "$lib/components/ErrorAlert.svelte"; | ||||
|   import { addToast } from "$lib/toast"; | ||||
| 
 | ||||
|   interface SignupResponse { | ||||
|     user: MeUser; | ||||
|  | @ -67,6 +68,22 @@ | |||
|       deleteError = e as APIError; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const linkAccount = async () => { | ||||
|     try { | ||||
|       const resp = await apiFetchClient<MeUser>("/auth/mastodon/add-provider", "POST", { | ||||
|         instance: data.instance, | ||||
|         ticket: data.ticket, | ||||
|       }); | ||||
| 
 | ||||
|       localStorage.setItem("pronouns-user", JSON.stringify(resp)); | ||||
|       userStore.set(resp); | ||||
|       addToast({ header: "Linked account", body: "Successfully linked account!" }); | ||||
|       goto("/settings/auth"); | ||||
|     } catch (e) { | ||||
|       data.error = e as APIError; | ||||
|     } | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <svelte:head> | ||||
|  | @ -78,7 +95,32 @@ | |||
| {#if data.error} | ||||
|   <ErrorAlert error={data.error} /> | ||||
| {/if} | ||||
| {#if data.ticket} | ||||
| {#if data.ticket && $userStore} | ||||
|   <div> | ||||
|     <label for="fediverse">Fediverse username</label> | ||||
|     <input | ||||
|       id="fediverse" | ||||
|       class="form-control" | ||||
|       name="fediverse" | ||||
|       readonly | ||||
|       value="{data.fediverse}@{data.instance}" | ||||
|     /> | ||||
|   </div> | ||||
|   <div> | ||||
|     <label for="fediverse">pronouns.cc username</label> | ||||
|     <input | ||||
|       id="pronounscc" | ||||
|       class="form-control" | ||||
|       name="pronounscc" | ||||
|       readonly | ||||
|       value={$userStore.name} | ||||
|     /> | ||||
|   </div> | ||||
|   <div> | ||||
|     <Button on:click={linkAccount}>Link account</Button> | ||||
|     <Button color="secondary" href="/settings/auth">Cancel</Button> | ||||
|   </div> | ||||
| {:else if data.ticket} | ||||
|   <form on:submit|preventDefault={signupForm}> | ||||
|     <div> | ||||
|       <label for="fediverse">Fediverse username</label> | ||||
|  | @ -86,7 +128,7 @@ | |||
|         id="fediverse" | ||||
|         class="form-control" | ||||
|         name="fediverse" | ||||
|         disabled | ||||
|         readonly | ||||
|         value="{data.fediverse}@{data.instance}" | ||||
|       /> | ||||
|     </div> | ||||
|  |  | |||
|  | @ -35,7 +35,14 @@ | |||
|         <ListGroupItem tag="a" active={$page.url.pathname === "/settings"} href="/settings"> | ||||
|           Your profile | ||||
|         </ListGroupItem> | ||||
|         {#if data.require_invite} | ||||
|         <ListGroupItem | ||||
|           tag="a" | ||||
|           active={$page.url.pathname === "/settings/auth"} | ||||
|           href="/settings/auth" | ||||
|         > | ||||
|           Authentication | ||||
|         </ListGroupItem> | ||||
|         {#if data.invitesEnabled} | ||||
|           <ListGroupItem | ||||
|             tag="a" | ||||
|             active={$page.url.pathname === "/settings/invites"} | ||||
|  |  | |||
|  | @ -1,8 +1,36 @@ | |||
| import { | ||||
|   ErrorCode, | ||||
|   type APIError, | ||||
|   type Invite, | ||||
|   type MeUser, | ||||
|   type PartialMember, | ||||
| } from "$lib/api/entities"; | ||||
| import { apiFetchClient } from "$lib/api/fetch"; | ||||
| import type { LayoutLoad } from "./$types"; | ||||
| 
 | ||||
| export const ssr = false; | ||||
| 
 | ||||
| export const load = (async ({ parent }) => { | ||||
|   const user = await apiFetchClient<MeUser>("/users/@me"); | ||||
|   const members = await apiFetchClient<PartialMember[]>("/users/@me/members"); | ||||
| 
 | ||||
|   let invites: Invite[] = []; | ||||
|   let invitesEnabled = true; | ||||
|   try { | ||||
|     invites = await apiFetchClient<Invite[]>("/auth/invites"); | ||||
|   } catch (e) { | ||||
|     if ((e as APIError).code === ErrorCode.InvitesDisabled) { | ||||
|       invitesEnabled = false; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const data = await parent(); | ||||
|   return data; | ||||
| 
 | ||||
|   return { | ||||
|     ...data, | ||||
|     user, | ||||
|     members, | ||||
|     invites, | ||||
|     invitesEnabled, | ||||
|   }; | ||||
| }) satisfies LayoutLoad; | ||||
|  |  | |||
|  | @ -1,30 +0,0 @@ | |||
| import { | ||||
|   type Invite, | ||||
|   type APIError, | ||||
|   type MeUser, | ||||
|   type PartialMember, | ||||
|   ErrorCode, | ||||
| } from "$lib/api/entities"; | ||||
| import { apiFetchClient } from "$lib/api/fetch"; | ||||
| import { error } from "@sveltejs/kit"; | ||||
| 
 | ||||
| export const load = async () => { | ||||
|   try { | ||||
|     const user = await apiFetchClient<MeUser>("/users/@me"); | ||||
|     const members = await apiFetchClient<PartialMember[]>("/users/@me/members"); | ||||
| 
 | ||||
|     let invites: Invite[] = []; | ||||
|     let invitesEnabled = true; | ||||
|     try { | ||||
|       invites = await apiFetchClient<Invite[]>("/auth/invites"); | ||||
|     } catch (e) { | ||||
|       if ((e as APIError).code === ErrorCode.InvitesDisabled) { | ||||
|         invitesEnabled = false; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return { user, members, invites, invitesEnabled }; | ||||
|   } catch (e) { | ||||
|     throw error(500, (e as APIError).message); | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										115
									
								
								frontend/src/routes/settings/auth/+page.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								frontend/src/routes/settings/auth/+page.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,115 @@ | |||
| <script lang="ts"> | ||||
|   import type { APIError } from "$lib/api/entities"; | ||||
|   import { apiFetch } from "$lib/api/fetch"; | ||||
|   import ErrorAlert from "$lib/components/ErrorAlert.svelte"; | ||||
|   import { | ||||
|     Button, | ||||
|     Card, | ||||
|     CardBody, | ||||
|     CardText, | ||||
|     CardTitle, | ||||
|     Input, | ||||
|     Modal, | ||||
|     ModalBody, | ||||
|     ModalFooter, | ||||
|   } from "sveltestrap"; | ||||
|   import type { PageData } from "./$types"; | ||||
| 
 | ||||
|   export let data: PageData; | ||||
| 
 | ||||
|   let canUnlink = false; | ||||
| 
 | ||||
|   $: canUnlink = | ||||
|     [data.user.discord, data.user.fediverse] | ||||
|       .map<number>((entry) => (entry === null ? 0 : 1)) | ||||
|       .reduce((prev, current) => prev + current) >= 2; | ||||
| 
 | ||||
|   let error: APIError | null = null; | ||||
|   let instance = ""; | ||||
|   let fediDisabled = false; | ||||
| 
 | ||||
|   let fediLinkModalOpen = false; | ||||
|   let toggleFediLinkModal = () => (fediLinkModalOpen = !fediLinkModalOpen); | ||||
| 
 | ||||
|   const fediLogin = async () => { | ||||
|     fediDisabled = true; | ||||
|     try { | ||||
|       const resp = await apiFetch<{ url: string }>( | ||||
|         `/auth/urls/fediverse?instance=${encodeURIComponent(instance)}`, | ||||
|         {}, | ||||
|       ); | ||||
|       window.location.assign(resp.url); | ||||
|     } catch (e) { | ||||
|       error = e as APIError; | ||||
|     } finally { | ||||
|       fediDisabled = false; | ||||
|     } | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <div> | ||||
|   <h1>Authentication providers</h1> | ||||
| 
 | ||||
|   <div> | ||||
|     <div class="my-2"> | ||||
|       <Card> | ||||
|         <CardBody> | ||||
|           <CardTitle>Fediverse</CardTitle> | ||||
|           <CardText> | ||||
|             {#if data.user.fediverse} | ||||
|               Your currently linked Fediverse account is <b | ||||
|                 >{data.user.fediverse_username}@{data.user.fediverse_instance}</b | ||||
|               > | ||||
|               (<code>{data.user.fediverse}</code>). | ||||
|             {:else} | ||||
|               You do not have a linked Fediverse account. | ||||
|             {/if} | ||||
|           </CardText> | ||||
|           {#if data.user.fediverse} | ||||
|             <Button color="danger" disabled={!canUnlink}>Unlink account</Button> | ||||
|           {:else} | ||||
|             <Button color="secondary" on:click={toggleFediLinkModal}>Link account</Button> | ||||
|           {/if} | ||||
|         </CardBody> | ||||
|       </Card> | ||||
|     </div> | ||||
|     <div class="my-2"> | ||||
|       <Card> | ||||
|         <CardBody> | ||||
|           <CardTitle>Discord</CardTitle> | ||||
|           <CardText> | ||||
|             {#if data.user.discord} | ||||
|               Your currently linked Discord account is <b>{data.user.discord_username}</b> | ||||
|               (<code>{data.user.discord}</code>). | ||||
|             {:else} | ||||
|               You do not have a linked Discord account. | ||||
|             {/if} | ||||
|           </CardText> | ||||
|           {#if data.user.discord} | ||||
|             <Button color="danger" disabled={!canUnlink}>Unlink account</Button> | ||||
|           {:else} | ||||
|             <Button color="secondary" href={data.urls.discord}>Link account</Button> | ||||
|           {/if} | ||||
|         </CardBody> | ||||
|       </Card> | ||||
|     </div> | ||||
|     <Modal header="Pick an instance" isOpen={fediLinkModalOpen} toggle={toggleFediLinkModal}> | ||||
|       <ModalBody> | ||||
|         <p> | ||||
|           <strong>Note:</strong> Misskey (and derivatives) are not supported yet, sorry. | ||||
|         </p> | ||||
|         <Input placeholder="Instance (e.g. mastodon.social)" bind:value={instance} /> | ||||
|         {#if error} | ||||
|           <div class="mt-2"> | ||||
|             <ErrorAlert {error} /> | ||||
|           </div> | ||||
|         {/if} | ||||
|       </ModalBody> | ||||
|       <ModalFooter> | ||||
|         <Button color="primary" disabled={fediDisabled || instance === ""} on:click={fediLogin} | ||||
|           >Log in</Button | ||||
|         > | ||||
|       </ModalFooter> | ||||
|     </Modal> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										17
									
								
								frontend/src/routes/settings/auth/+page.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								frontend/src/routes/settings/auth/+page.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| import { PUBLIC_BASE_URL } from "$env/static/public"; | ||||
| import { apiFetch } from "$lib/api/fetch"; | ||||
| 
 | ||||
| export const load = async () => { | ||||
|   const resp = await apiFetch<UrlsResponse>("/auth/urls", { | ||||
|     method: "POST", | ||||
|     body: { | ||||
|       callback_domain: PUBLIC_BASE_URL, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   return { urls: resp }; | ||||
| }; | ||||
| 
 | ||||
| interface UrlsResponse { | ||||
|   discord: string; | ||||
| } | ||||
|  | @ -1,25 +0,0 @@ | |||
| import { ErrorCode, type APIError, type Invite } from "$lib/api/entities"; | ||||
| import { apiFetchClient } from "$lib/api/fetch"; | ||||
| import { error } from "@sveltejs/kit"; | ||||
| import type { PageLoad } from "../$types"; | ||||
| 
 | ||||
| export const load = (async () => { | ||||
|   const data = { | ||||
|     invitesEnabled: true, | ||||
|     invites: [] as Invite[], | ||||
|   }; | ||||
| 
 | ||||
|   try { | ||||
|     const invites = await apiFetchClient<Invite[]>("/auth/invites"); | ||||
|     data.invites = invites; | ||||
|   } catch (e) { | ||||
|     if ((e as APIError).code === ErrorCode.InvitesDisabled) { | ||||
|       data.invitesEnabled = false; | ||||
|       data.invites = []; | ||||
|     } else { | ||||
|       throw error((e as APIError).code, (e as APIError).message); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return data; | ||||
| }) satisfies PageLoad; | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue