Skip to content

Commit

Permalink
Update handles (bluesky-social#547)
Browse files Browse the repository at this point in the history
* change pkey on user table to did & rename to user_account

* migration

* tidy

* fixes suggested by bryn

* missed merge thing

* some updating hanldes

scheams

* impl + passing test

* more handle tests

* tidy

* update did doc + some new tests

* one more test

* test handle casing

* Fix did pkey down migration

---------

Co-authored-by: Devin Ivy <[email protected]>
  • Loading branch information
dholms and devinivy authored Feb 15, 2023
1 parent a24be34 commit 57da227
Show file tree
Hide file tree
Showing 13 changed files with 396 additions and 34 deletions.
20 changes: 20 additions & 0 deletions lexicons/com/atproto/handle/update.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"lexicon": 1,
"id": "com.atproto.handle.update",
"defs": {
"main": {
"type": "procedure",
"description": "Updates the handle of the account",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["handle"],
"properties": {
"handle": {"type": "string"}
}
}
}
}
}
}
13 changes: 13 additions & 0 deletions packages/api/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep
import * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction'
import * as ComAtprotoBlobUpload from './types/com/atproto/blob/upload'
import * as ComAtprotoHandleResolve from './types/com/atproto/handle/resolve'
import * as ComAtprotoHandleUpdate from './types/com/atproto/handle/update'
import * as ComAtprotoRepoBatchWrite from './types/com/atproto/repo/batchWrite'
import * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createRecord'
import * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord'
Expand Down Expand Up @@ -115,6 +116,7 @@ export * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep
export * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction'
export * as ComAtprotoBlobUpload from './types/com/atproto/blob/upload'
export * as ComAtprotoHandleResolve from './types/com/atproto/handle/resolve'
export * as ComAtprotoHandleUpdate from './types/com/atproto/handle/update'
export * as ComAtprotoRepoBatchWrite from './types/com/atproto/repo/batchWrite'
export * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createRecord'
export * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord'
Expand Down Expand Up @@ -498,6 +500,17 @@ export class HandleNS {
throw ComAtprotoHandleResolve.toKnownErr(e)
})
}

update(
data?: ComAtprotoHandleUpdate.InputSchema,
opts?: ComAtprotoHandleUpdate.CallOptions,
): Promise<ComAtprotoHandleUpdate.Response> {
return this._service.xrpc
.call('com.atproto.handle.update', opts?.qp, data, opts)
.catch((e) => {
throw ComAtprotoHandleUpdate.toKnownErr(e)
})
}
}

export class RepoNS {
Expand Down
23 changes: 23 additions & 0 deletions packages/api/src/client/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,28 @@ export const schemaDict = {
},
},
},
ComAtprotoHandleUpdate: {
lexicon: 1,
id: 'com.atproto.handle.update',
defs: {
main: {
type: 'procedure',
description: 'Updates the handle of the account',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['handle'],
properties: {
handle: {
type: 'string',
},
},
},
},
},
},
},
ComAtprotoRepoBatchWrite: {
lexicon: 1,
id: 'com.atproto.repo.batchWrite',
Expand Down Expand Up @@ -4171,6 +4193,7 @@ export const ids = {
ComAtprotoAdminTakeModerationAction: 'com.atproto.admin.takeModerationAction',
ComAtprotoBlobUpload: 'com.atproto.blob.upload',
ComAtprotoHandleResolve: 'com.atproto.handle.resolve',
ComAtprotoHandleUpdate: 'com.atproto.handle.update',
ComAtprotoRepoBatchWrite: 'com.atproto.repo.batchWrite',
ComAtprotoRepoCreateRecord: 'com.atproto.repo.createRecord',
ComAtprotoRepoDeleteRecord: 'com.atproto.repo.deleteRecord',
Expand Down
31 changes: 31 additions & 0 deletions packages/api/src/client/types/com/atproto/handle/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { Headers, XRPCError } from '@atproto/xrpc'
import { ValidationResult } from '@atproto/lexicon'
import { isObj, hasProp } from '../../../../util'
import { lexicons } from '../../../../lexicons'

export interface QueryParams {}

export interface InputSchema {
handle: string
[k: string]: unknown
}

export interface CallOptions {
headers?: Headers
qp?: QueryParams
encoding: 'application/json'
}

export interface Response {
success: boolean
headers: Headers
}

export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
}
return e
}
68 changes: 68 additions & 0 deletions packages/pds/src/api/com/atproto/handle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { InvalidRequestError } from '@atproto/xrpc-server'
import * as handleLib from '@atproto/handle'
import { Server } from '../../../lexicon'
import AppContext from '../../../context'
import { UserAlreadyExistsError } from '../../../services/actor'

export default function (server: Server, ctx: AppContext) {
server.com.atproto.handle.resolve(async ({ params }) => {
const handle = params.handle

let did = ''
if (!handle || handle === ctx.cfg.publicHostname) {
// self
did = ctx.cfg.serverDid
} else {
const supportedHandle = ctx.cfg.availableUserDomains.some((host) =>
handle.endsWith(host),
)
if (!supportedHandle) {
throw new InvalidRequestError('Not a supported handle domain')
}
const user = await ctx.services.actor(ctx.db).getUser(handle, true)
if (!user) {
throw new InvalidRequestError('Unable to resolve handle')
}
did = user.did
}

return {
encoding: 'application/json',
body: { did },
}
})

server.com.atproto.handle.update({
auth: ctx.accessVerifierCheckTakedown,
handler: async ({ auth, input }) => {
const requester = auth.credentials.did
let handle: string
try {
handle = handleLib.normalizeAndEnsureValid(
input.body.handle,
ctx.cfg.availableUserDomains,
)
} catch (err) {
if (err instanceof handleLib.InvalidHandleError) {
throw new InvalidRequestError(err.message, 'InvalidHandle')
} else if (err instanceof handleLib.ReservedHandleError) {
throw new InvalidRequestError(err.message, 'HandleNotAvailable')
}
throw err
}

await ctx.db.transaction(async (dbTxn) => {
try {
await ctx.services.actor(dbTxn).updateHandle(requester, handle)
} catch (err) {
if (err instanceof UserAlreadyExistsError) {
throw new InvalidRequestError(`Handle already taken: ${handle}`)
}
throw err
}

await ctx.plcClient.updateHandle(requester, handle, ctx.keypair)
})
},
})
}
32 changes: 0 additions & 32 deletions packages/pds/src/api/com/atproto/handles.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/pds/src/api/com/atproto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import account from './account'
import admin from './admin'
import blob from './blob'
import getAccountsConfig from './getAccountsConfig'
import handles from './handles'
import handle from './handle'
import repo from './repo'
import report from './report'
import session from './session'
Expand All @@ -15,7 +15,7 @@ export default function (server: Server, ctx: AppContext) {
admin(server, ctx)
blob(server, ctx)
getAccountsConfig(server, ctx)
handles(server, ctx)
handle(server, ctx)
repo(server, ctx)
report(server, ctx)
session(server, ctx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export async function down(db: Kysely<any>): Promise<void> {
.column('passwordResetToken')
.execute()

await db.schema.dropTable('user_state').execute()
await db.schema.dropIndex('user_account_email_lower_idx').execute()
await db.schema.dropIndex('user_account_password_reset_token_idx').execute()
await db.schema.dropTable('user_account').execute()
Expand Down
8 changes: 8 additions & 0 deletions packages/pds/src/lexicon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep
import * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction'
import * as ComAtprotoBlobUpload from './types/com/atproto/blob/upload'
import * as ComAtprotoHandleResolve from './types/com/atproto/handle/resolve'
import * as ComAtprotoHandleUpdate from './types/com/atproto/handle/update'
import * as ComAtprotoRepoBatchWrite from './types/com/atproto/repo/batchWrite'
import * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createRecord'
import * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord'
Expand Down Expand Up @@ -327,6 +328,13 @@ export class HandleNS {
const nsid = 'com.atproto.handle.resolve' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}

update<AV extends AuthVerifier>(
cfg: ConfigOf<AV, ComAtprotoHandleUpdate.Handler<ExtractAuth<AV>>>,
) {
const nsid = 'com.atproto.handle.update' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
}

export class RepoNS {
Expand Down
23 changes: 23 additions & 0 deletions packages/pds/src/lexicon/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,28 @@ export const schemaDict = {
},
},
},
ComAtprotoHandleUpdate: {
lexicon: 1,
id: 'com.atproto.handle.update',
defs: {
main: {
type: 'procedure',
description: 'Updates the handle of the account',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['handle'],
properties: {
handle: {
type: 'string',
},
},
},
},
},
},
},
ComAtprotoRepoBatchWrite: {
lexicon: 1,
id: 'com.atproto.repo.batchWrite',
Expand Down Expand Up @@ -4171,6 +4193,7 @@ export const ids = {
ComAtprotoAdminTakeModerationAction: 'com.atproto.admin.takeModerationAction',
ComAtprotoBlobUpload: 'com.atproto.blob.upload',
ComAtprotoHandleResolve: 'com.atproto.handle.resolve',
ComAtprotoHandleUpdate: 'com.atproto.handle.update',
ComAtprotoRepoBatchWrite: 'com.atproto.repo.batchWrite',
ComAtprotoRepoCreateRecord: 'com.atproto.repo.createRecord',
ComAtprotoRepoDeleteRecord: 'com.atproto.repo.deleteRecord',
Expand Down
34 changes: 34 additions & 0 deletions packages/pds/src/lexicon/types/com/atproto/handle/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import express from 'express'
import { ValidationResult } from '@atproto/lexicon'
import { lexicons } from '../../../../lexicons'
import { isObj, hasProp } from '../../../../util'
import { HandlerAuth } from '@atproto/xrpc-server'

export interface QueryParams {}

export interface InputSchema {
handle: string
[k: string]: unknown
}

export interface HandlerInput {
encoding: 'application/json'
body: InputSchema
}

export interface HandlerError {
status: number
message?: string
}

export type HandlerOutput = HandlerError | void
export type Handler<HA extends HandlerAuth = never> = (ctx: {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}) => Promise<HandlerOutput> | HandlerOutput
17 changes: 17 additions & 0 deletions packages/pds/src/services/actor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,23 @@ export class ActorService {
log.info({ handle, email, did }, 'registered user')
}

async updateHandle(did: string, handle: string) {
const res = await this.db.db
.updateTable('did_handle')
.set({ handle })
.where('did', '=', did)
.whereNotExists(
this.db.db
.selectFrom('did_handle')
.where('handle', '=', handle)
.selectAll(),
)
.executeTakeFirst()
if (res.numUpdatedRows < 1) {
throw new UserAlreadyExistsError()
}
}

async updateUserPassword(did: string, password: string) {
const passwordScrypt = await scrypt.hash(password)
await this.db.db
Expand Down
Loading

0 comments on commit 57da227

Please sign in to comment.