Skip to content

Commit ee5500e

Browse files
committed
feat(auth): implement update handle functionality and add identity scope
1 parent 5b203cb commit ee5500e

3 files changed

Lines changed: 82 additions & 26 deletions

File tree

app/pages/account/settings.vue

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,53 @@
11
<script setup lang="ts">
22
const { user } = useAtproto()
33
4-
const isResetting = ref(false)
4+
// --- Password Reset States ---
5+
const isResettingEmail = ref(false)
56
const resetEmail = ref('')
67
const isCodeSent = ref(false)
78
const resetToken = ref('')
89
const newPassword = ref('')
910
const isConfirming = ref(false)
11+
const passwordError = ref('')
12+
const passwordSuccess = ref('')
1013
11-
const errorMessage = ref('')
12-
const successMessage = ref('')
14+
// --- Handle Update States ---
15+
const newHandle = ref('')
16+
const isUpdatingHandle = ref(false)
17+
const handleError = ref('')
18+
const handleSuccess = ref('')
1319
14-
async function handlePasswordReset() {
15-
errorMessage.value = ''
16-
successMessage.value = ''
20+
async function requestPasswordReset() {
21+
passwordError.value = ''
22+
passwordSuccess.value = ''
1723
1824
if (!resetEmail.value) {
19-
errorMessage.value = 'Please enter your email first.'
25+
passwordError.value = 'Please enter your email first.'
2026
return
2127
}
2228
23-
isResetting.value = true
29+
isResettingEmail.value = true
2430
try {
2531
await $fetch('/api/atproto/password-reset', {
2632
method: 'POST',
2733
body: { email: resetEmail.value },
2834
})
2935
3036
isCodeSent.value = true
31-
successMessage.value = 'Code sent! Check your email.'
37+
passwordSuccess.value = 'Code sent! Check your email.'
3238
} catch (e: any) {
33-
errorMessage.value = e.statusMessage || 'Something went wrong. Please try again.'
39+
passwordError.value = e.statusMessage || 'Something went wrong. Please try again.'
3440
} finally {
35-
isResetting.value = false
41+
isResettingEmail.value = false
3642
}
3743
}
3844
39-
async function handleConfirmReset() {
40-
errorMessage.value = ''
41-
successMessage.value = ''
45+
async function confirmPasswordReset() {
46+
passwordError.value = ''
47+
passwordSuccess.value = ''
4248
4349
if (!resetToken.value || !newPassword.value) {
44-
errorMessage.value = 'Please enter both the code and your new password.'
50+
passwordError.value = 'Please enter both the code and your new password.'
4551
return
4652
}
4753
@@ -55,19 +61,42 @@ async function handleConfirmReset() {
5561
},
5662
})
5763
58-
successMessage.value = 'Password updated successfully!'
64+
passwordSuccess.value = 'Password updated successfully!'
5965
60-
// Reset the form
6166
isCodeSent.value = false
6267
resetToken.value = ''
6368
newPassword.value = ''
6469
resetEmail.value = ''
6570
} catch (e: any) {
66-
errorMessage.value = e.statusMessage || 'Failed to update password. Check your code.'
71+
passwordError.value = e.statusMessage || 'Failed to update password. Check your code.'
6772
} finally {
6873
isConfirming.value = false
6974
}
7075
}
76+
77+
async function updateHandle() {
78+
handleError.value = ''
79+
handleSuccess.value = ''
80+
81+
if (!newHandle.value) {
82+
handleError.value = 'Please enter your new handle first'
83+
return
84+
}
85+
86+
isUpdatingHandle.value = true
87+
try {
88+
await $fetch('/api/atproto/handle-update', {
89+
method: 'POST',
90+
body: { handle: newHandle.value },
91+
})
92+
93+
handleSuccess.value = 'Handle updated!'
94+
} catch (e: any) {
95+
handleError.value = e.statusMessage || 'Something went wrong. Please try again'
96+
} finally {
97+
isUpdatingHandle.value = false
98+
}
99+
}
71100
</script>
72101

73102
<template>
@@ -97,11 +126,12 @@ async function handleConfirmReset() {
97126
</p>
98127
<div class="flex gap-4">
99128
<input
129+
v-model="newHandle"
100130
type="text"
101131
class="flex-1 bg-bg border border-border rounded-md px-3 py-2 font-mono text-sm max-w-md"
102132
:placeholder="user?.handle || 'New handle...'"
103133
/>
104-
<ButtonBase variant="primary">Update Handle</ButtonBase>
134+
<ButtonBase variant="primary" @click="updateHandle">Update Handle</ButtonBase>
105135
</div>
106136
</section>
107137

@@ -120,8 +150,8 @@ async function handleConfirmReset() {
120150
>
121151
<h3 class="font-mono text-lg text-fg">Reset Password</h3>
122152

123-
<p v-if="errorMessage" class="text-sm text-red-500 mb-2">{{ errorMessage }}</p>
124-
<p v-if="successMessage" class="text-sm text-green-500 mb-2">{{ successMessage }}</p>
153+
<p v-if="passwordError" class="text-sm text-red-500 mb-2">{{ passwordError }}</p>
154+
<p v-if="passwordSuccess" class="text-sm text-green-500 mb-2">{{ passwordSuccess }}</p>
125155

126156
<div v-if="!isCodeSent">
127157
<p class="text-sm text-fg-muted mb-2">
@@ -136,10 +166,10 @@ async function handleConfirmReset() {
136166
<ButtonBase
137167
variant="secondary"
138168
class="text-red-500 mt-2"
139-
:disabled="isResetting || !resetEmail"
140-
@click="handlePasswordReset"
169+
:disabled="isResettingEmail || !resetEmail"
170+
@click="requestPasswordReset"
141171
>
142-
{{ isResetting ? 'Sending...' : 'Send Reset Code' }}
172+
{{ isResettingEmail ? 'Sending...' : 'Send Reset Code' }}
143173
</ButtonBase>
144174
</div>
145175

@@ -162,7 +192,7 @@ async function handleConfirmReset() {
162192
variant="secondary"
163193
class="text-red-500 mt-2"
164194
:disabled="isConfirming || !resetToken || !newPassword"
165-
@click="handleConfirmReset"
195+
@click="confirmPasswordReset"
166196
>
167197
{{ isConfirming ? 'Saving...' : 'Save New Password' }}
168198
</ButtonBase>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Agent } from '@atproto/api'
2+
3+
export default eventHandlerWithOAuthSession(async (event, oAuthSession) => {
4+
if (!oAuthSession) {
5+
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
6+
}
7+
8+
const body = await readBody(event)
9+
if (!body || !body.handle) {
10+
throw createError({ statusCode: 400, statusMessage: 'Handle is required' })
11+
}
12+
13+
const agent = new Agent(oAuthSession)
14+
15+
try {
16+
await agent.com.atproto.identity.updateHandle({
17+
handle: body.handle,
18+
})
19+
return { success: true }
20+
} catch (err: any) {
21+
throw createError({
22+
statusCode: 500,
23+
statusMessage: err.message || 'Failed to update handle.',
24+
})
25+
}
26+
})

server/utils/atproto/oauth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type { UserServerSession } from '#shared/types/userSession'
1515
import { clientUri } from '#oauth/config'
1616

1717
// TODO: If you add writing a new record you will need to add a scope for it
18-
export const scope = `atproto ${LIKES_SCOPE} ${PROFILE_SCOPE}`
18+
export const scope = `atproto ${LIKES_SCOPE} ${PROFILE_SCOPE} identity:handle`
1919

2020
/**
2121
* Resolves a did to a handle via DoH or via the http website calls

0 commit comments

Comments
 (0)