Skip to content

Commit 477cebb

Browse files
committed
working atproto oauth login and logout
1 parent 8955ae6 commit 477cebb

9 files changed

Lines changed: 1264 additions & 904 deletions

File tree

app/components/AppHeader.vue

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,22 +137,15 @@ onKeyStroke(',', e => {
137137
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
138138
aria-hidden="true"
139139
>
140-
<span class="i-carbon-logo-github w-4 h-4" />
141-
<span class="hidden sm:inline">github</span>
142-
</a>
143-
</li>
144-
145-
<li>
146-
<AuthButton />
147-
</li>
148-
</ul>
149140
,
150141
</kbd>
151142
</NuxtLink>
152143

153144
<div v-if="showConnector" class="hidden sm:block">
154145
<ConnectorStatus />
155146
</div>
147+
148+
<AuthButton />
156149
</div>
157150
</nav>
158151
</header>

app/components/AuthButton.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
const showModal = ref(false)
3+
const { user } = await useAtproto()
34
</script>
45

56
<template>
@@ -10,7 +11,7 @@ const showModal = ref(false)
1011
:aria-label="ariaLabel"
1112
@click="showModal = true"
1213
>
13-
Login
14+
{{ user?.miniDoc?.handle || 'Login' }}
1415
</button>
1516

1617
<AuthModal v-model:open="showModal" />

app/components/AuthModal.vue

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const open = defineModel<boolean>('open', { default: false })
33
44
const handleInput = ref('')
55
6+
const { user, logout } = await useAtproto()
7+
68
async function handleLogin() {
79
if (handleInput.value) {
810
await navigateTo(
@@ -53,8 +55,25 @@ async function handleLogin() {
5355
</button>
5456
</div>
5557

58+
<div v-if="user?.miniDoc?.handle" class="space-y-4">
59+
<div class="flex items-center gap-3 p-4 bg-bg-subtle border border-border rounded-lg">
60+
<span class="w-3 h-3 rounded-full bg-green-500" aria-hidden="true" />
61+
<div>
62+
<p class="font-mono text-xs text-fg-muted">
63+
Logged in as @{{ user.miniDoc.handle }}
64+
</p>
65+
</div>
66+
<button
67+
@click="logout"
68+
class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-all duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 focus-visible:ring-offset-2 focus-visible:ring-offset-bg"
69+
>
70+
Logout
71+
</button>
72+
</div>
73+
</div>
74+
5675
<!-- Disconnected state -->
57-
<form class="space-y-4" @submit.prevent="handleLogin">
76+
<form v-else class="space-y-4" @submit.prevent="handleLogin">
5877
<p class="text-sm text-fg-muted">Login with your Atmosphere account</p>
5978

6079
<div class="space-y-3">

app/composables/useAtproto.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
type MiniDoc = {
2+
did: string
3+
handle: string
4+
pds: string
5+
}
6+
7+
export async function useAtproto() {
8+
const {
9+
data: user,
10+
pending,
11+
clear,
12+
} = await useAsyncData<MiniDoc | null>('user-state', async () => {
13+
const data = await useRequestFetch()<MiniDoc>('/api/auth/session', {
14+
headers: { accept: 'application/json' },
15+
})
16+
17+
return data
18+
})
19+
20+
const logout = async () => {
21+
await useRequestFetch()<MiniDoc>('/api/auth/session', {
22+
method: 'delete',
23+
headers: { accept: 'application/json' },
24+
})
25+
26+
clear()
27+
}
28+
29+
return { user, pending, logout }
30+
}

pnpm-lock.yaml

Lines changed: 1172 additions & 888 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/api/auth/atproto.get.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,27 @@ export default defineEventHandler(async event => {
3535
return sendRedirect(event, redirectUrl.toString())
3636
}
3737

38-
const { session } = await atclient.callback(new URLSearchParams(query as Record<string, string>))
39-
const agent = new Agent(session)
38+
const { session: authSession } = await atclient.callback(
39+
new URLSearchParams(query as Record<string, string>),
40+
)
41+
const agent = new Agent(authSession)
4042
event.context.agent = agent
41-
console.log(agent.did)
43+
44+
const session = await useSession(event, {
45+
password: process.env.NUXT_SESSION_PASSWORD as string,
46+
})
47+
48+
const response = await fetch(
49+
`https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${agent.did}`,
50+
)
51+
const miniDoc = (await response.json()) as { did: string; handle: string; pds: string }
52+
53+
await session.update({
54+
miniDoc,
55+
})
56+
57+
await sessionStore.del()
58+
4259
return sendRedirect(event, '/')
4360
})
4461

server/api/auth/session.delete.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default defineEventHandler(async event => {
2+
const session = await useSession(event, {
3+
password: process.env.NUXT_SESSION_PASSWORD as string,
4+
})
5+
6+
await session.clear()
7+
8+
return 'Session cleared'
9+
})

server/api/auth/session.get.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default defineEventHandler(async event => {
2+
const session = await useSession(event, {
3+
password: process.env.NUXT_SESSION_PASSWORD as string,
4+
})
5+
6+
return session.data
7+
})

server/utils/atproto.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { OAuthClientMetadataInput } from '@atproto/oauth-client-node'
1+
import type { OAuthClientMetadataInput } from '@atproto/oauth-client-node'
22

33
// TODO: limit scope as features gets added
44
export const scope = 'atproto transition:generic'
55

66
export function getOauthClientMetadata() {
77
const dev = import.meta.dev
88

9-
// TODO: CHECK - on dev, match in nuxt.config.ts devServer: { host: "127.0.0.1" }
9+
// on dev, match in nuxt.config.ts devServer: { host: "127.0.0.1" }
1010
const client_uri = dev ? `http://127.0.0.1:3000` : 'https://npmx.dev'
1111
const redirect_uri = `${client_uri}/api/auth/atproto`
1212

0 commit comments

Comments
 (0)