mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
feat: update auth flow (#5790)
* Backend routes for choosing username in OAuth flow * fix up oauth flow routes * improve URL-related OAuth code * Use user-provided callback addr instead of SELF_ADDR * Revert "Use user-provided callback addr instead of SELF_ADDR" This reverts commit7ea0635d86. * fix flow * fix: backend response for create oauth account * feat: new auth flow (#5840) * update auth with new designs * refactor: auth.js to auth.ts * refactor: componentize auth pages * fix: auth pages height * feat: initial implementation of new sign-in oauth * fix create account flow * fix checkbox * remove hard coded username * implement create user validation endpoint and add more specific error responses * feat: implement under 13 DOB guard and email/password validation route * fix: TOCTOU issue * refactor: pnpm prepr * fix: make sure staging uses staging * fix: hcaptcha styles * fix: copy * remove: auth/welcome page as its no longer used * refactor: bring root page card styles into individual components and use tailwind * fix: account settings modals to use new modal and fix lots of bad styles * refactor: pnpm prepr * feat: implement last signed in indicator * fix: append number when generated name from email is taken * refactor: pnpm prepr * fix: last sign in badge color * fix: qa issues * refactor: pnpm prepr * fix: hover effect on native date picker * chore: temp staging undo * Revert "chore: temp staging undo" This reverts commitcad6bd4f92. * feat: handle app create account * fix: last signed in style * fix: add initOnMounted for SSR race * refactor: use typescript * refactor: pnpm prepr * refactor: use typescript for reset-password * refactor: convert verify-email to use typescript * refactor: convert authorize.vue to use typescript * fix: authorize.vue error states * feat: small style updates * feat: implement date picker component * feat: improve UX and styles for range select * refactor: pnpm prepr * fix: range select border styles * feat: implement date picker component in create account * feat: implement preserve date for date picker * update rust toolchain * increase recursion limit * fix: date picker can be null * fix: calculate age based on user's timezone * fix: number input icons color * fix: date picker icons * feat: improve styles * fix: add width on date * fix: hover color bad on number input * fix lints * feat: add default date open view * fmt * fix: account.vue * fix: remove default date to open 13 years ago * fix: edit copy on info banner * fix: cannot hover over project card tooltip items (#6071) fix: cannot hover over project cards * feat: improve add dependency flow (#6075) * fix: shadow on nav * feat: improve add dependency flow * feat: update suggested dependency style * feat: update dependency rows to use version number and update styles * feat: implement combobox select searched text on focus * feat: add Tabs.vue * feat: update nav tabs to use tabs * feat: improve project search dropdown * fix: dependency search not clearing inbound query * fix: combobox no options open state bug * feat: improve dependency project and version search * fix: open modrinth project links in the app (#6072) * pin tanstack versions + set pnpm min age to 7 days * squash commits * fix: 2 factor auth enter code screen styles * update copy * update copy * improve reset password * feat: update sign in screen * fix: unused import * Merge branch 'main' into boris/dev-908-backend-changes * Revert "Merge branch 'main' into boris/dev-908-backend-changes" This reverts commitb9b03796e3. * fix: add stroke * feat: add passkey support (#6375) * feat: add passkey backend * feat: passkey frontend * invalidate sessions on compromised passkey * chore: run sqlx prepare * fix: make passkey button use both collumns to prevent empty space * fix: correctly verify max passkeys in finish route * fix: use structs for response * fix: add rp name default * style: use web::Json * fmt * feat: improve manage passkeys UI * fix copy * pnpm prepr --------- Co-authored-by: tdgao <mr.trumgao@gmail.com> Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: Michael H. <michael@iptables.sh> Co-authored-by: Calum H. (IMB11) <contact@cal.engineer> Co-authored-by: Calum H. <calum@modrinth.com> Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com> Co-authored-by: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com>
This commit is contained in:
co-authored by
tdgao
Truman Gao
Michael H.
Calum H.
Calum H.
Prospector
DeDiamondPro
parent
6fc741f7c0
commit
ef4044534f
@@ -57,6 +57,39 @@ export class LabrinthAuthV2Module extends AbstractModule {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate email/password inputs for account creation without creating an account.
|
||||
*
|
||||
* @param data - Prospective account credentials
|
||||
*/
|
||||
public async validateCreateAccount(
|
||||
data: Labrinth.Auth.v2.ValidateCreateAccountRequest,
|
||||
): Promise<void> {
|
||||
return this.client.request(`/auth/create/validate`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new account from an OAuth callback flow state
|
||||
*
|
||||
* @param data - OAuth account creation data
|
||||
* @returns Promise resolving to a session response
|
||||
*/
|
||||
public async createOAuthAccount(
|
||||
data: Labrinth.Auth.v2.CreateOAuthAccountRequest,
|
||||
): Promise<Labrinth.Auth.v2.CreateOAuthAccountResponse> {
|
||||
return this.client.request<Labrinth.Auth.v2.CreateOAuthAccountResponse>(`/auth/create/oauth`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin a password reset flow by sending a recovery email
|
||||
*
|
||||
@@ -84,4 +117,116 @@ export class LabrinthAuthV2Module extends AbstractModule {
|
||||
body: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* List the current user's registered passkeys
|
||||
*
|
||||
* @returns A promise that resolves to a list of the user's registered passkeys
|
||||
*/
|
||||
public async listPasskeys(): Promise<Labrinth.Auth.v2.Passkey[]> {
|
||||
return this.client.request<Labrinth.Auth.v2.Passkey[]>(`/auth/passkey`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin registering a new passkey, returning the WebAuthn creation options and a flow
|
||||
*
|
||||
* @returns A promise that resolves to the WebAuthn creation options and flow
|
||||
*/
|
||||
public async registerPasskeyStart(): Promise<Labrinth.Auth.v2.PasskeyRegisterStartResponse> {
|
||||
return this.client.request<Labrinth.Auth.v2.PasskeyRegisterStartResponse>(
|
||||
`/auth/passkey/register/start`,
|
||||
{
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'POST',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete passkey registration with the created credential
|
||||
*
|
||||
* @param data The credential data and flow to complete registration with
|
||||
* @returns A promise that resolves to the newly registered passkey
|
||||
*/
|
||||
public async registerPasskeyFinish(
|
||||
data: Labrinth.Auth.v2.PasskeyRegisterFinishRequest,
|
||||
): Promise<Labrinth.Auth.v2.Passkey> {
|
||||
return this.client.request<Labrinth.Auth.v2.Passkey>(`/auth/passkey/register/finish`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin a passkey authentication flow, returning the WebAuthn request options and a flow
|
||||
*
|
||||
* @returns A promise that resolves to the WebAuthn request options and a flow
|
||||
*/
|
||||
public async authenticatePasskeyStart(): Promise<Labrinth.Auth.v2.PasskeyAuthenticateStartResponse> {
|
||||
return this.client.request<Labrinth.Auth.v2.PasskeyAuthenticateStartResponse>(
|
||||
`/auth/passkey/start`,
|
||||
{
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'POST',
|
||||
skipAuth: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a passkey authentication flow, returning the new session
|
||||
*
|
||||
* @param data The credential data and flow to complete authentication with
|
||||
* @returns A promise that resolves to the new session
|
||||
*/
|
||||
public async authenticatePasskeyFinish(
|
||||
data: Labrinth.Auth.v2.PasskeyAuthenticateFinishRequest,
|
||||
): Promise<Labrinth.Sessions.v2.Session> {
|
||||
return this.client.request<Labrinth.Sessions.v2.Session>(`/auth/passkey/finish`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'POST',
|
||||
body: data,
|
||||
skipAuth: true,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a passkey
|
||||
*
|
||||
* @param id The ID of the passkey to rename
|
||||
* @param data The new name for the passkey
|
||||
*/
|
||||
public async renamePasskey(
|
||||
id: string,
|
||||
data: Labrinth.Auth.v2.PasskeyRenameRequest,
|
||||
): Promise<void> {
|
||||
return this.client.request(`/auth/passkey/${id}`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'PATCH',
|
||||
body: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a passkey
|
||||
*
|
||||
* @param id The ID of the passkey to delete
|
||||
*/
|
||||
public async deletePasskey(id: string): Promise<void> {
|
||||
return this.client.request(`/auth/passkey/${id}`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,6 +695,23 @@ export namespace Labrinth {
|
||||
session: string
|
||||
}
|
||||
|
||||
export type ValidateCreateAccountRequest = {
|
||||
username: string
|
||||
password: string
|
||||
email: string
|
||||
}
|
||||
|
||||
export type CreateOAuthAccountRequest = {
|
||||
username: string
|
||||
state: string
|
||||
challenge: string
|
||||
sign_up_newsletter: boolean
|
||||
}
|
||||
|
||||
export type CreateOAuthAccountResponse = {
|
||||
session: string
|
||||
}
|
||||
|
||||
export type ResetPasswordRequest = {
|
||||
username: string
|
||||
challenge: string
|
||||
@@ -705,6 +722,38 @@ export namespace Labrinth {
|
||||
old_password?: string
|
||||
new_password?: string
|
||||
}
|
||||
|
||||
export type Passkey = {
|
||||
id: string
|
||||
name: string
|
||||
created_at: string
|
||||
last_used: string | null
|
||||
}
|
||||
|
||||
export type PasskeyRegisterStartResponse = {
|
||||
options: Record<string, unknown>
|
||||
flow: string
|
||||
}
|
||||
|
||||
export type PasskeyRegisterFinishRequest = {
|
||||
flow: string
|
||||
name: string
|
||||
credential: unknown
|
||||
}
|
||||
|
||||
export type PasskeyAuthenticateStartResponse = {
|
||||
options: Record<string, unknown>
|
||||
flow: string
|
||||
}
|
||||
|
||||
export type PasskeyAuthenticateFinishRequest = {
|
||||
flow: string
|
||||
credential: unknown
|
||||
}
|
||||
|
||||
export type PasskeyRenameRequest = {
|
||||
name: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -418,6 +418,7 @@ import _UpdatedIcon from './icons/updated.svg?component'
|
||||
import _UploadIcon from './icons/upload.svg?component'
|
||||
import _UserIcon from './icons/user.svg?component'
|
||||
import _UserCogIcon from './icons/user-cog.svg?component'
|
||||
import _UserKeyIcon from './icons/user-key.svg?component'
|
||||
import _UserPlusIcon from './icons/user-plus.svg?component'
|
||||
import _UserRoundIcon from './icons/user-round.svg?component'
|
||||
import _UserSearchIcon from './icons/user-search.svg?component'
|
||||
@@ -848,6 +849,7 @@ export const UpdatedIcon = _UpdatedIcon
|
||||
export const UploadIcon = _UploadIcon
|
||||
export const UserIcon = _UserIcon
|
||||
export const UserCogIcon = _UserCogIcon
|
||||
export const UserKeyIcon = _UserKeyIcon
|
||||
export const UserPlusIcon = _UserPlusIcon
|
||||
export const UserRoundIcon = _UserRoundIcon
|
||||
export const UserSearchIcon = _UserSearchIcon
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-key-icon lucide-user-key"><path d="M20 11v6"/><path d="M20 13h2"/><path d="M3 21v-2a4 4 0 0 1 4-4h6a4 4 0 0 1 2.072.578"/><circle cx="10" cy="7" r="4"/><circle cx="20" cy="19" r="2"/></svg>
|
||||
|
After Width: | Height: | Size: 396 B |
@@ -14,7 +14,7 @@
|
||||
@click="toggle"
|
||||
>
|
||||
<span
|
||||
class="w-5 h-5 rounded-md flex items-center justify-center border-[1px] border-solid shrink-0"
|
||||
class="w-5 h-5 aspect-square rounded-md flex shrink-0 items-center justify-center border-[1px] border-solid"
|
||||
:class="{
|
||||
'bg-brand border-button-border text-brand-inverted': modelValue,
|
||||
'bg-surface-2 border-surface-5 text-primary': !modelValue,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<slot name="title" />
|
||||
</div>
|
||||
|
||||
<div class="grid-area-[description] flex flex-col gap-[var(--gap-md)]">
|
||||
<div class="grid-area-[description] flex flex-col gap-3">
|
||||
<slot name="description" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
data-pyro-server-list-root
|
||||
class="relative mx-auto mb-6 flex w-full flex-col p-6"
|
||||
:class="serverList.length ? 'min-h-screen' : 'min-h-[calc(100vh-4.5rem)]'"
|
||||
:class="serverList.length ? 'min-h-screen' : 'min-h-[calc(100vh-14.5rem)]'"
|
||||
>
|
||||
<ServersGuestPlanModal
|
||||
ref="guestPlanModal"
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
"defaultMessage": "Re-install modpack"
|
||||
},
|
||||
"button.remove": {
|
||||
"defaultMessage": "Remove"
|
||||
"defaultMessage": "Delete passkey"
|
||||
},
|
||||
"button.remove-image": {
|
||||
"defaultMessage": "Remove image"
|
||||
|
||||
@@ -245,7 +245,7 @@ export const commonMessages = defineMessages({
|
||||
},
|
||||
removeButton: {
|
||||
id: 'button.remove',
|
||||
defaultMessage: 'Remove',
|
||||
defaultMessage: 'Delete passkey',
|
||||
},
|
||||
removeImageButton: {
|
||||
id: 'button.remove-image',
|
||||
|
||||
Reference in New Issue
Block a user