Name Resolution ()

* user-did table

* yay fixed it

* resolve available domains from db

* serverDid + tests

* check for invalid domains

* mv available domain check to constructor

* oops reverse check
This commit is contained in:
Daniel Holmgren 2022-10-28 18:06:17 -05:00 committed by GitHub
parent b3966734de
commit ac8569496c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 494 additions and 482 deletions

@ -19,7 +19,7 @@
"type": "array",
"items": {
"type": "object",
"required": ["did", "name", "createdAt", "indexedAt"],
"required": ["did", "name"],
"properties": {
"did": {"type": "string"},
"name": {"type": "string"},
@ -28,7 +28,6 @@
"maxLength": 64
},
"description": {"type": "string"},
"createdAt": {"type": "string", "format": "date-time"},
"indexedAt": {"type": "string", "format": "date-time"}
}
}

@ -1,7 +1,6 @@
import http from 'http'
import chalk from 'chalk'
import crytpo from 'crypto'
import { MemoryBlockstore } from '@atproto/repo'
import PDSServer, { Database as PDSDatabase } from '@atproto/pds'
import * as plc from '@atproto/plc'
import * as crypto from '@atproto/crypto'
@ -60,6 +59,14 @@ export class DevEnvServer {
await db.migrateToLatestOrThrow()
const keypair = await crypto.EcdsaKeypair.create()
const plcClient = new plc.PlcClient(this.env.plcUrl)
const serverDid = await plcClient.createDid(
keypair,
keypair.did(),
'localhost',
`http://localhost:${this.port}`,
)
this.inst = await onServerReady(
PDSServer(db, keypair, {
debugMode: true,
@ -67,9 +74,10 @@ export class DevEnvServer {
hostname: 'localhost',
port: this.port,
didPlcUrl: this.env.plcUrl,
serverDid,
recoveryKey: keypair.did(),
testNameRegistry: this.env.testNameRegistry,
jwtSecret: crytpo.randomBytes(8).toString('base64'),
availableUserDomains: ['.test'],
appUrlPasswordReset: 'app://password-reset',
// @TODO setup ethereal.email creds and set emailSmtpUrl here
emailNoReplyAddress: 'noreply@blueskyweb.xyz',
@ -111,7 +119,6 @@ export class DevEnvServer {
export class DevEnv {
plcUrl: string | undefined
servers: Map<number, DevEnvServer> = new Map()
testNameRegistry: Record<string, string> = {}
static async create(params: StartParams): Promise<DevEnv> {
const devEnv = new DevEnv()

@ -19,17 +19,17 @@ export default function (server: Server) {
}
const userLookupCol = author.startsWith('did:')
? 'user.did'
: 'user.username'
? 'user_did.did'
: 'user_did.username'
const userQb = db.db
.selectFrom('user')
.selectFrom('user_did')
.selectAll()
.where(userLookupCol, '=', author)
const postsQb = db.db
.selectFrom('app_bsky_post')
.whereExists(
userQb.whereRef('user.did', '=', ref('app_bsky_post.creator')),
userQb.whereRef('user_did.did', '=', ref('app_bsky_post.creator')),
)
.select([
sql<'post' | 'repost'>`${'post'}`.as('type'),
@ -42,7 +42,7 @@ export default function (server: Server) {
const repostsQb = db.db
.selectFrom('app_bsky_repost')
.whereExists(
userQb.whereRef('user.did', '=', ref('app_bsky_repost.creator')),
userQb.whereRef('user_did.did', '=', ref('app_bsky_repost.creator')),
)
.select([
sql<'post' | 'repost'>`${'repost'}`.as('type'),
@ -56,13 +56,13 @@ export default function (server: Server) {
.selectFrom(postsQb.union(repostsQb).as('posts_and_reposts'))
.innerJoin('app_bsky_post as post', 'post.uri', 'postUri')
.innerJoin('ipld_block', 'ipld_block.cid', 'post.cid')
.innerJoin('user as author', 'author.did', 'post.creator')
.innerJoin('user_did as author', 'author.did', 'post.creator')
.leftJoin(
'app_bsky_profile as author_profile',
'author_profile.creator',
'author.did',
)
.innerJoin('user as originator', 'originator.did', 'originatorDid')
.innerJoin('user_did as originator', 'originator.did', 'originatorDid')
.leftJoin(
'app_bsky_profile as originator_profile',
'originator_profile.creator',

@ -22,15 +22,19 @@ export default function (server: Server) {
'accept.badgeUri',
'badge.uri',
)
.innerJoin('user', 'user.did', 'accept.creator')
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user.did')
.innerJoin('user_did', 'user_did.did', 'accept.creator')
.leftJoin(
'app_bsky_profile as profile',
'profile.creator',
'user_did.did',
)
.where('badge.uri', '=', uri)
.whereRef('offer.creator', '=', 'badge.creator')
.whereRef('offer.subject', '=', 'accept.creator')
.whereRef('accept.offerUri', '=', 'offer.uri')
.select([
'user.did as did',
'user.username as name',
'user_did.did as did',
'user_did.username as name',
'profile.displayName as displayName',
'offer.createdAt as offeredAt',
'accept.createdAt as acceptedAt',

@ -72,13 +72,13 @@ export default function (server: Server) {
.selectFrom(postsQb.union(repostsQb).as('posts_and_reposts'))
.innerJoin('app_bsky_post as post', 'post.uri', 'postUri')
.innerJoin('ipld_block', 'ipld_block.cid', 'post.cid')
.innerJoin('user as author', 'author.did', 'post.creator')
.innerJoin('user_did as author', 'author.did', 'post.creator')
.leftJoin(
'app_bsky_profile as author_profile',
'author_profile.creator',
'author.did',
)
.innerJoin('user as originator', 'originator.did', 'originatorDid')
.innerJoin('user_did as originator', 'originator.did', 'originatorDid')
.leftJoin(
'app_bsky_profile as originator_profile',
'originator_profile.creator',

@ -13,11 +13,15 @@ export default function (server: Server) {
let builder = db.db
.selectFrom('app_bsky_like as like')
.where('like.subject', '=', uri)
.innerJoin('user', 'like.creator', 'user.did')
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user.did')
.innerJoin('user_did', 'like.creator', 'user_did.did')
.leftJoin(
'app_bsky_profile as profile',
'profile.creator',
'user_did.did',
)
.select([
'user.did as did',
'user.username as name',
'user_did.did as did',
'user_did.username as name',
'profile.displayName as displayName',
'like.createdAt as createdAt',
'like.indexedAt as indexedAt',

@ -17,8 +17,9 @@ export default function (server: Server) {
const result = await db.db
.selectFrom('user_notification as notif')
.select(countAll.as('count'))
.innerJoin('user', 'notif.userDid', 'user.did')
.where('user.did', '=', requester)
.innerJoin('user_did', 'user_did.did', 'notif.userDid')
.innerJoin('user', 'user.username', 'user_did.username')
.where('user_did.did', '=', requester)
.whereRef('notif.indexedAt', '>', 'user.lastSeenNotifs')
.executeTakeFirst()

@ -21,7 +21,7 @@ export default function (server: Server) {
.selectFrom('user_notification as notif')
.where('notif.userDid', '=', requester)
.innerJoin('ipld_block', 'ipld_block.cid', 'notif.recordCid')
.innerJoin('user as author', 'author.did', 'notif.author')
.innerJoin('user_did as author', 'author.did', 'notif.author')
.leftJoin(
'app_bsky_profile as author_profile',
'author_profile.creator',
@ -50,6 +50,7 @@ export default function (server: Server) {
const [user, notifs] = await Promise.all([
db.db
.selectFrom('user')
.innerJoin('user_did', 'user_did.username', 'user.username')
.selectAll()
.where('did', '=', requester)
.executeTakeFirst(),

@ -74,7 +74,7 @@ const postInfoBuilder = (db: Kysely<DatabaseSchema>, requester: string) => {
return db
.selectFrom('app_bsky_post as post')
.innerJoin('ipld_block', 'ipld_block.cid', 'post.cid')
.innerJoin('user as author', 'author.did', 'post.creator')
.innerJoin('user_did as author', 'author.did', 'post.creator')
.leftJoin(
'app_bsky_profile as author_profile',
'author.did',

@ -18,34 +18,38 @@ export default function (server: Server) {
const { ref } = db.db.dynamic
const queryRes = await db.db
.selectFrom('user')
.selectFrom('user_did')
.where(userWhereClause(user))
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user.did')
.leftJoin(
'app_bsky_profile as profile',
'profile.creator',
'user_did.did',
)
.select([
'user.did as did',
'user.username as name',
'user_did.did as did',
'user_did.username as name',
'profile.uri as profileUri',
'profile.displayName as displayName',
'profile.description as description',
db.db
.selectFrom('app_bsky_follow')
.whereRef('creator', '=', ref('user.did'))
.whereRef('creator', '=', ref('user_did.did'))
.select(countAll.as('count'))
.as('followsCount'),
db.db
.selectFrom('app_bsky_follow')
.whereRef('subject', '=', ref('user.did'))
.whereRef('subject', '=', ref('user_did.did'))
.select(countAll.as('count'))
.as('followersCount'),
db.db
.selectFrom('app_bsky_post')
.whereRef('creator', '=', ref('user.did'))
.whereRef('creator', '=', ref('user_did.did'))
.select(countAll.as('count'))
.as('postsCount'),
db.db
.selectFrom('app_bsky_follow')
.where('creator', '=', requester)
.whereRef('subject', '=', ref('user.did'))
.whereRef('subject', '=', ref('user_did.did'))
.select('uri')
.as('requesterFollow'),
])
@ -73,7 +77,7 @@ export default function (server: Server) {
'accept.badgeUri',
'badge.uri',
)
.innerJoin('user as issuer', 'issuer.did', 'badge.creator')
.innerJoin('user_did as issuer', 'issuer.did', 'badge.creator')
.where('offer.subject', '=', queryRes.did)
.whereRef('offer.creator', '=', 'badge.creator')
.whereRef('accept.offerUri', '=', 'offer.uri')

@ -13,11 +13,15 @@ export default function (server: Server) {
let builder = db.db
.selectFrom('app_bsky_repost as repost')
.where('repost.subject', '=', uri)
.innerJoin('user', 'repost.creator', 'user.did')
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user.did')
.innerJoin('user_did', 'user_did.did', 'repost.creator')
.leftJoin(
'app_bsky_profile as profile',
'profile.creator',
'user_did.did',
)
.select([
'user.did as did',
'user.username as name',
'user_did.did as did',
'user_did.username as name',
'profile.displayName as displayName',
'repost.createdAt as createdAt',
'repost.indexedAt as indexedAt',

@ -19,7 +19,7 @@ export default function (server: Server) {
let followersReq = db.db
.selectFrom('app_bsky_follow as follow')
.where('follow.subject', '=', subject.did)
.innerJoin('user as creator', 'creator.did', 'follow.creator')
.innerJoin('user_did as creator', 'creator.did', 'follow.creator')
.leftJoin(
'app_bsky_profile as profile',
'profile.creator',

@ -19,7 +19,7 @@ export default function (server: Server) {
let followsReq = db.db
.selectFrom('app_bsky_follow as follow')
.where('follow.creator', '=', creator.did)
.innerJoin('user as subject', 'subject.did', 'follow.subject')
.innerJoin('user_did as subject', 'subject.did', 'follow.subject')
.leftJoin(
'app_bsky_profile as profile',
'profile.creator',

@ -39,8 +39,7 @@ export default function (server: Server) {
name: result.name,
displayName: result.displayName ?? undefined,
description: result.description ?? undefined,
createdAt: result.createdAt,
indexedAt: result.indexedAt ?? result.createdAt,
indexedAt: result.indexedAt ?? undefined,
}))
const lastResult = results.at(-1)
@ -57,14 +56,13 @@ export default function (server: Server) {
const getResultsPg: GetResultsFn = async (db, { term, limit, before }) => {
return await getUserSearchQueryPg(db, { term, limit, before })
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user.did')
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user_did.did')
.select([
'distance',
'user.did as did',
'user.username as name',
'user_did.did as did',
'user_did.username as name',
'profile.displayName as displayName',
'profile.description as description',
'user.createdAt as createdAt',
'profile.indexedAt as indexedAt',
])
.execute()
@ -72,14 +70,13 @@ const getResultsPg: GetResultsFn = async (db, { term, limit, before }) => {
const getResultsSqlite: GetResultsFn = async (db, { term, limit, before }) => {
return await getUserSearchQuerySqlite(db, { term, limit, before })
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user.did')
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user_did.did')
.select([
sql<number>`0`.as('distance'),
'user.did as did',
'user.username as name',
'user_did.did as did',
'user_did.username as name',
'profile.displayName as displayName',
'profile.description as description',
'user.createdAt as createdAt',
'profile.indexedAt as indexedAt',
])
.execute()
@ -95,7 +92,6 @@ type GetResultsFn = (
displayName: string | null
description: string | null
distance: number
createdAt: string
indexedAt: string | null
}[]
>

@ -48,10 +48,10 @@ export default function (server: Server) {
const getResultsPg: GetResultsFn = async (db, { term, limit }) => {
return await getUserSearchQueryPg(db, { term, limit })
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user.did')
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user_did.did')
.select([
'user.did as did',
'user.username as name',
'user_did.did as did',
'user_did.username as name',
'profile.displayName as displayName',
])
.execute()
@ -59,10 +59,10 @@ const getResultsPg: GetResultsFn = async (db, { term, limit }) => {
const getResultsSqlite: GetResultsFn = async (db, { term, limit }) => {
return await getUserSearchQuerySqlite(db, { term, limit })
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user.did')
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user_did.did')
.select([
'user.did as did',
'user.username as name',
'user_did.did as did',
'user_did.username as name',
'profile.displayName as displayName',
])
.execute()

@ -19,16 +19,17 @@ export default function (server: Server) {
throw new InvalidRequestError('Invalid date')
}
const result = await db.db
.updateTable('user')
.set({ lastSeenNotifs: parsed })
.where('did', '=', requester)
.executeTakeFirst()
if (Number(result.numUpdatedRows) < 1) {
const user = await db.getUser(requester)
if (!user) {
throw new InvalidRequestError(`Could not find user: ${requester}`)
}
await db.db
.updateTable('user')
.set({ lastSeenNotifs: parsed })
.where('username', '=', user.username)
.executeTakeFirst()
return { encoding: 'application/json', body: {} }
})
}

@ -13,12 +13,12 @@ export const getUserInfo = async (
user: string,
): Promise<UserInfo> => {
const userInfo = await db
.selectFrom('user')
.selectFrom('user_did')
.where(util.userWhereClause(user))
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user.did')
.leftJoin('app_bsky_profile as profile', 'profile.creator', 'user_did.did')
.select([
'user.did as did',
'user.username as name',
'user_did.did as did',
'user_did.username as name',
'profile.displayName as displayName',
])
.executeTakeFirst()

@ -24,10 +24,10 @@ export const getUserSearchQueryPg = (
distance: distanceAccount,
})
const accountsQb = db.db
.selectFrom('user')
.selectFrom('user_did')
.where(distanceAccount, '<', threshold)
.if(!!keysetAccount, (qb) => (keysetAccount ? qb.where(keysetAccount) : qb))
.select(['user.did as did', distanceAccount.as('distance')])
.select(['user_did.did as did', distanceAccount.as('distance')])
.orderBy(distanceAccount)
.orderBy('username')
.limit(limit)
@ -40,10 +40,10 @@ export const getUserSearchQueryPg = (
})
const profilesQb = db.db
.selectFrom('app_bsky_profile')
.innerJoin('user', 'user.did', 'app_bsky_profile.creator')
.innerJoin('user_did', 'user_did.did', 'app_bsky_profile.creator')
.where(distanceProfile, '<', threshold)
.if(!!keysetProfile, (qb) => (keysetProfile ? qb.where(keysetProfile) : qb))
.select(['user.did as did', distanceProfile.as('distance')])
.select(['user_did.did as did', distanceProfile.as('distance')])
.orderBy(distanceProfile)
.orderBy('username')
.limit(limit)
@ -72,7 +72,7 @@ export const getUserSearchQueryPg = (
})
return db.db
.selectFrom(resultsQb.as('results'))
.innerJoin('user', 'user.did', 'results.did')
.innerJoin('user_did', 'user_did.did', 'results.did')
.if(!!keysetAll, (qb) => (keysetAll ? qb.where(keysetAll) : qb))
.orderBy('distance')
.orderBy('username') // Keep order stable: break ties in distance arbitrarily using username
@ -98,19 +98,19 @@ export const getUserSearchQuerySqlite = (
if (!safeWords.length) {
// Return no results. This could happen with weird input like ' % _ '.
return db.db.selectFrom('user').where(sql`1 = 0`)
return db.db.selectFrom('user_did').where(sql`1 = 0`)
}
// We'll ensure there's a space before each word in both textForMatch and in safeWords,
// so that we can reliably match word prefixes using LIKE operator.
const textForMatch = sql`lower(' ' || ${ref(
'user.username',
'user_did.username',
)} || ' ' || coalesce(${ref('profile.displayName')}, ''))`
const cursor = before !== undefined ? unpackCursor(before) : undefined
return db.db
.selectFrom('user')
.selectFrom('user_did')
.where((q) => {
safeWords.forEach((word) => {
// Match word prefixes against contents of username and displayName

@ -1,9 +1,6 @@
import assert from 'assert'
import { randomBytes } from '@atproto/crypto'
import { InvalidRequestError } from '@atproto/xrpc-server'
import { Repo } from '@atproto/repo'
import { PlcClient } from '@atproto/plc'
import * as uint8arrays from 'uint8arrays'
import { Server } from '../../../lexicon'
import * as locals from '../../../locals'
import { countAll } from '../../../db/util'
@ -14,13 +11,7 @@ export default function (server: Server) {
server.com.atproto.getAccountsConfig((_params, _input, _req, res) => {
const cfg = locals.config(res)
let availableUserDomains: string[]
if (cfg.debugMode && !!cfg.testNameRegistry) {
availableUserDomains = ['test']
} else {
throw new Error('TODO')
}
const availableUserDomains = cfg.availableUserDomains
const inviteCodeRequired = cfg.inviteRequired
return {
@ -40,7 +31,6 @@ export default function (server: Server) {
// In order to perform the significant db updates ahead of
// registering the did, we will use a temp invalid did. Once everything
// goes well and a fresh did is registered, we'll replace the temp values.
const tempDid = uint8arrays.toString(randomBytes(16), 'base32')
const now = new Date().toISOString()
// Validate username
@ -52,17 +42,17 @@ export default function (server: Server) {
)
}
let isTestUser = false
if (username.endsWith('.test')) {
if (!config.debugMode || !config.testNameRegistry) {
throw new InvalidRequestError(
'Cannot register a test user if debug mode is not enabled',
)
}
isTestUser = true
const supportedUsername = config.availableUserDomains.some((host) =>
username.toLowerCase().endsWith(host),
)
if (!supportedUsername) {
throw new InvalidRequestError(
'Not a supported username domain',
'InvalidUsername',
)
}
const { did } = await db.transaction(async (dbTxn) => {
const did = await db.transaction(async (dbTxn) => {
if (config.inviteRequired) {
if (!inviteCode) {
throw new InvalidRequestError(
@ -92,21 +82,12 @@ export default function (server: Server) {
'InvalidInviteCode',
)
}
await dbTxn.db
.insertInto('invite_code_use')
.values({
code: inviteCode,
usedBy: tempDid,
usedAt: now,
})
.execute()
}
// Pre-register user before going out to PLC to get a real did
try {
await dbTxn.preRegisterUser(email, username, tempDid, password)
await dbTxn.registerUser(email, username, password)
} catch (err) {
if (err instanceof UserAlreadyExistsError) {
if ((await dbTxn.getUser(username)) !== null) {
@ -143,17 +124,16 @@ export default function (server: Server) {
// tables, and setup the repo root. These all _should_ succeed under typical conditions.
// It's about as good as we're gonna get transactionally, given that we rely on PLC here to assign the did.
await dbTxn.postRegisterUser(tempDid, did)
if (config.inviteRequired) {
const updated = await dbTxn.db
.updateTable('invite_code_use')
.where('usedBy', '=', tempDid)
.set({ usedBy: did })
.executeTakeFirst()
assert(
Number(updated.numUpdatedRows) === 1,
'Should act on exactly one invite code use',
)
await dbTxn.registerUserDid(username, did)
if (config.inviteRequired && inviteCode) {
await dbTxn.db
.insertInto('invite_code_use')
.values({
code: inviteCode,
usedBy: did,
usedAt: now,
})
.execute()
}
// Setup repo root
@ -170,13 +150,9 @@ export default function (server: Server) {
})
.execute()
return { did, isTestUser }
return did
})
if (isTestUser && config.testNameRegistry) {
config.testNameRegistry[username] = did
}
const jwt = auth.createToken(did)
return { encoding: 'application/json', body: { jwt, username, did } }
})

@ -3,20 +3,27 @@ import { InvalidRequestError } from '@atproto/xrpc-server'
import * as locals from '../../../locals'
export default function (server: Server) {
server.com.atproto.resolveName((params, _in, _req, res) => {
const { config, keypair } = locals.get(res)
server.com.atproto.resolveName(async (params, _in, _req, res) => {
const { db, config } = locals.get(res)
const name = params.name
let did = ''
if (!params.name || params.name === config.hostname) {
if (!name || name === config.hostname) {
// self
did = keypair.did()
} else if (params.name.endsWith('.test') && config.testNameRegistry) {
did = config.testNameRegistry[params.name]
did = config.serverDid
} else {
// @TODO
}
if (!did) {
throw new InvalidRequestError(`Unable to resolve name`)
const supportedUsername = config.availableUserDomains.some((host) =>
name.endsWith(host),
)
if (!supportedUsername) {
throw new InvalidRequestError('Not a supported username domain')
}
const user = await db.getUser(name)
if (!user) {
throw new InvalidRequestError('Unable to resolve namej')
}
did = user.did
}
return {

@ -67,7 +67,7 @@ export default function (server: Server) {
// Token had correct scope, was not expired, and referenced
// a user whose password has not changed since token issuance.
await db.updateUserPassword(did, password)
await db.updateUserPassword(user.username, password)
return {
encoding: 'application/json',

@ -25,19 +25,19 @@ export default function (server: Server) {
server.com.atproto.createSession(async (_params, input, _req, res) => {
const { username, password } = input.body
const { db, auth } = locals.get(res)
const did = await db.verifyUserPassword(username, password)
if (did === null) {
const validPass = await db.verifyUserPassword(username, password)
if (!validPass) {
throw new AuthRequiredError('Invalid username or password')
}
const user = await db.getUser(did)
const user = await db.getUser(username)
if (!user) {
throw new InvalidRequestError(
`Could not find user info for account: ${did}`,
`Could not find user info for account: ${username}`,
)
}
const jwt = auth.createToken(did)
const jwt = auth.createToken(user.did)
return {
encoding: 'application/json',
body: { jwt, name: user.username, did: user.did },

@ -13,6 +13,7 @@ export interface ServerConfigValues {
didPlcUrl: string
serverDid: string
recoveryKey: string
adminPassword: string
@ -21,7 +22,7 @@ export interface ServerConfigValues {
blockstoreLocation?: string
databaseLocation?: string
testNameRegistry?: Record<string, string>
availableUserDomains: string[]
appUrlPasswordReset: string
emailSmtpUrl?: string
@ -29,9 +30,16 @@ export interface ServerConfigValues {
}
export class ServerConfig {
constructor(private cfg: ServerConfigValues) {}
constructor(private cfg: ServerConfigValues) {
const invalidDomain = cfg.availableUserDomains.find(
(domain) => domain.length < 1 || !domain.startsWith('.'),
)
if (invalidDomain) {
throw new Error(`Invalid domain: ${invalidDomain}`)
}
}
static readEnv(overrides?: Partial<ServerConfig>) {
static readEnv(overrides?: Partial<ServerConfigValues>) {
const debugMode = process.env.DEBUG_MODE === '1'
const publicUrl = process.env.PUBLIC_URL || undefined
@ -49,6 +57,11 @@ export class ServerConfig {
const didPlcUrl = process.env.DID_PLC_URL || 'http://localhost:2582'
const serverDid = overrides?.serverDid || process.env.SERVER_DID
if (typeof serverDid !== 'string') {
throw new Error('No value provided for process.env.SERVER_DID')
}
const recoveryKey = overrides?.recoveryKey || process.env.RECOVERY_KEY
if (typeof recoveryKey !== 'string') {
throw new Error('No value provided for process.env.RECOVERY_KEY')
@ -61,7 +74,9 @@ export class ServerConfig {
const blockstoreLocation = process.env.BLOCKSTORE_LOC
const databaseLocation = process.env.DATABASE_LOC
const testNameRegistry = debugMode ? {} : undefined
const availableUserDomains = process.env.AVAILABLE_USER_DOMAINS
? process.env.AVAILABLE_USER_DOMAINS.split(',')
: []
const appUrlPasswordReset =
process.env.APP_URL_PASSWORD_RESET || 'app://password-reset'
@ -85,11 +100,12 @@ export class ServerConfig {
jwtSecret,
recoveryKey,
didPlcUrl,
serverDid,
adminPassword,
inviteRequired,
blockstoreLocation,
databaseLocation,
testNameRegistry,
availableUserDomains,
appUrlPasswordReset,
emailSmtpUrl,
emailNoReplyAddress,
@ -138,6 +154,10 @@ export class ServerConfig {
return this.cfg.didPlcUrl
}
get serverDid() {
return this.cfg.recoveryKey
}
get recoveryKey() {
return this.cfg.recoveryKey
}
@ -166,8 +186,8 @@ export class ServerConfig {
return !this.databaseLocation
}
get testNameRegistry() {
return this.cfg.testNameRegistry
get availableUserDomains() {
return this.cfg.availableUserDomains
}
get appUrlPasswordReset() {

@ -1,4 +1,5 @@
import * as user from './tables/user'
import * as userDid from './tables/user-did'
import * as repoRoot from './tables/repo-root'
import * as record from './tables/record'
import * as ipldBlock from './tables/ipld-block'
@ -15,6 +16,7 @@ import * as badgeAccept from './records/badgeAccept'
import * as badgeOffer from './records/badgeOffer'
export type DatabaseSchema = user.PartialDB &
userDid.PartialDB &
repoRoot.PartialDB &
record.PartialDB &
ipldBlock.PartialDB &

@ -31,6 +31,7 @@ import { User } from './tables/user'
import { dummyDialect } from './util'
import * as migrations from './migrations'
import { CtxMigrationProvider } from './migrations/provider'
import { UserDid } from './tables/user-did'
export class Database {
migrator: Migrator
@ -186,13 +187,16 @@ export class Database {
}
}
async getUser(usernameOrDid: string): Promise<User | null> {
let query = this.db.selectFrom('user').selectAll()
async getUser(usernameOrDid: string): Promise<(User & UserDid) | null> {
let query = this.db
.selectFrom('user')
.innerJoin('user_did', 'user_did.username', 'user.username')
.selectAll()
if (usernameOrDid.startsWith('did:')) {
query = query.where('did', '=', usernameOrDid)
} else {
query = query.where(
sql`lower(username)`,
sql`lower(user_did.username)`,
'=',
usernameOrDid.toLowerCase(),
)
@ -201,9 +205,10 @@ export class Database {
return found || null
}
async getUserByEmail(email: string): Promise<User | null> {
async getUserByEmail(email: string): Promise<(User & UserDid) | null> {
const found = await this.db
.selectFrom('user')
.innerJoin('user_did', 'user_did.username', 'user.username')
.selectAll()
.where(sql`lower(email)`, '=', email.toLowerCase())
.executeTakeFirst()
@ -216,74 +221,57 @@ export class Database {
return found ? found.did : null
}
// Registration occurs in two steps:
// - pre-registration, we setup the account with an invalid, temporary did which is only visible in a transaction.
// - post-registration, we replace the temporary did with the user's newly-generated valid did.
async preRegisterUser(
email: string,
username: string,
tempDid: string,
password: string,
) {
async registerUser(email: string, username: string, password: string) {
this.assertTransaction()
log.debug({ username, email, tempDid }, 'pre-registering user')
log.debug({ username, email }, 'registering user')
const inserted = await this.db
.insertInto('user')
.values({
email: email,
username: username,
did: tempDid,
password: await scrypt.hash(password),
createdAt: new Date().toISOString(),
lastSeenNotifs: new Date().toISOString(),
})
.onConflict((oc) => oc.doNothing())
.returning('did')
.returning('username')
.executeTakeFirst()
if (!inserted) {
throw new UserAlreadyExistsError()
}
log.info({ username, email, tempDid }, 'pre-registered user')
log.info({ username, email }, 'registered user')
}
async postRegisterUser(tempDid: string, did: string) {
async registerUserDid(username: string, did: string) {
this.assertTransaction()
log.debug({ tempDid, did }, 'post-registering user')
const updated = await this.db
.updateTable('user')
.where('did', '=', tempDid)
.set({ did })
log.debug({ username, did }, 'registering user did')
await this.db
.insertInto('user_did')
.values({ username, did })
.executeTakeFirst()
assert(
Number(updated.numUpdatedRows) === 1,
'Post-register should act on exactly one user',
)
log.info({ tempDid, did }, 'post-registered user')
log.info({ username, did }, 'post-registered user')
}
async updateUserPassword(did: string, password: string) {
async updateUserPassword(username: string, password: string) {
const hashedPassword = await scrypt.hash(password)
await this.db
.updateTable('user')
.set({ password: hashedPassword })
.where('did', '=', did)
.where('username', '=', username)
.execute()
}
async verifyUserPassword(
username: string,
password: string,
): Promise<string | null> {
): Promise<boolean> {
const found = await this.db
.selectFrom('user')
.selectAll()
.where('username', '=', username)
.executeTakeFirst()
if (!found) return null
const validPass = await scrypt.verify(password, found.password)
if (!validPass) return null
return found.did
if (!found) return false
return scrypt.verify(password, found.password)
}
validateRecord(collection: string, obj: unknown): ValidationResult {

@ -2,6 +2,7 @@ import { Kysely, sql } from 'kysely'
import { Dialect } from '..'
const userTable = 'user'
const userDidTable = 'user_did'
const repoRootTable = 'repo_root'
const recordTable = 'record'
const ipldBlockTable = 'ipld_block'
@ -41,8 +42,7 @@ export async function up(db: Kysely<unknown>, dialect: Dialect): Promise<void> {
// Users
await db.schema
.createTable(userTable)
.addColumn('did', 'varchar', (col) => col.primaryKey())
.addColumn('username', 'varchar', (col) => col.notNull())
.addColumn('username', 'varchar', (col) => col.primaryKey())
.addColumn('email', 'varchar', (col) => col.notNull())
.addColumn('password', 'varchar', (col) => col.notNull())
.addColumn('lastSeenNotifs', 'varchar', (col) => col.notNull())
@ -60,10 +60,16 @@ export async function up(db: Kysely<unknown>, dialect: Dialect): Promise<void> {
.on(userTable)
.expression(sql`lower("email")`)
.execute()
// User Dids
await db.schema
.createTable(userDidTable)
.addColumn('did', 'varchar', (col) => col.primaryKey())
.addColumn('username', 'varchar', (col) => col.unique())
.execute()
if (dialect === 'pg') {
await db.schema // Supports user search
.createIndex(`${userTable}_username_tgrm_idx`)
.on(userTable)
.createIndex(`${userDidTable}_username_tgrm_idx`)
.on(userDidTable)
.using('gist')
.expression(sql`"username" gist_trgm_ops`)
.execute()
@ -263,5 +269,6 @@ export async function down(db: Kysely<unknown>): Promise<void> {
await db.schema.dropTable(ipldBlockTable).execute()
await db.schema.dropTable(recordTable).execute()
await db.schema.dropTable(repoRootTable).execute()
await db.schema.dropTable(userDidTable).execute()
await db.schema.dropTable(userTable).execute()
}

@ -67,12 +67,7 @@ const getFn =
const insertFn =
(db: Kysely<PartialDB>) =>
async (
uri: AtUri,
cid: CID,
obj: unknown,
timestamp?: string,
): Promise<void> => {
async (uri: AtUri, cid: CID, obj: unknown): Promise<void> => {
if (!matchesSchema(obj)) {
throw new Error(`Record does not match schema: ${type}`)
}
@ -88,7 +83,7 @@ const insertFn =
creator: uri.host,
displayName: obj.displayName,
description: obj.description,
indexedAt: timestamp || new Date().toISOString(),
indexedAt: new Date().toISOString(),
}
const promises = [
db.insertInto('app_bsky_profile').values(profile).execute(),

@ -0,0 +1,8 @@
export interface UserDid {
did: string
username: string
}
export const tableName = 'user_did'
export type PartialDB = { [tableName]: UserDid }

@ -1,5 +1,4 @@
export interface User {
did: string
username: string
email: string
password: string

@ -11,9 +11,9 @@ import {
export const userWhereClause = (user: string) => {
if (user.startsWith('did:')) {
return sql<0 | 1>`"user"."did" = ${user}`
return sql<0 | 1>`"user_did"."did" = ${user}`
} else {
return sql<0 | 1>`"user"."username" = ${user}`
return sql<0 | 1>`"user_did"."username" = ${user}`
}
}

@ -2397,7 +2397,7 @@ export const methodSchemaDict: Record<string, MethodSchema> = {
type: 'array',
items: {
type: 'object',
required: ['did', 'name', 'createdAt', 'indexedAt'],
required: ['did', 'name'],
properties: {
did: {
type: 'string',
@ -2412,10 +2412,6 @@ export const methodSchemaDict: Record<string, MethodSchema> = {
description: {
type: 'string',
},
createdAt: {
type: 'string',
format: 'date-time',
},
indexedAt: {
type: 'string',
format: 'date-time',

@ -30,8 +30,7 @@ export interface OutputSchema {
name: string,
displayName?: string,
description?: string,
createdAt: string,
indexedAt: string,
indexedAt?: string,
}[];
}

@ -1,2 +1,2 @@
LOG_ENABLED=false
LOG_ENABLED=true
LOG_DESTINATION=test.log

@ -33,17 +33,27 @@ export const runTestServer = async (
const plcServer = plc.server(plcDb, plcPort)
const recoveryKey = (await crypto.EcdsaKeypair.create()).did()
const plcClient = new plc.PlcClient(plcUrl)
const serverDid = await plcClient.createDid(
keypair,
recoveryKey,
'localhost',
`http://localhost:${pdsPort}`,
)
const config = new ServerConfig({
debugMode: true,
scheme: 'http',
hostname: 'localhost',
port: pdsPort,
serverDid,
recoveryKey,
adminPassword: ADMIN_PASSWORD,
inviteRequired: false,
didPlcUrl: plcUrl,
jwtSecret: 'jwt-secret',
testNameRegistry: {},
availableUserDomains: ['.test'],
appUrlPasswordReset: 'app://forgot-password',
emailNoReplyAddress: 'noreply@blueskyweb.xyz',
publicUrl: 'https://pds.public.url',

@ -87,7 +87,7 @@ describe('account', () => {
it('serves the accounts system config', async () => {
const res = await client.com.atproto.getAccountsConfig({})
expect(res.data.inviteCodeRequired).toBe(true)
expect(res.data.availableUserDomains[0]).toBe('test')
expect(res.data.availableUserDomains[0]).toBe('.test')
expect(typeof res.data.inviteCodeRequired).toBe('boolean')
})
@ -154,7 +154,7 @@ describe('account', () => {
{},
{
email: 'custom-recovery@test.com',
username: 'custom-recovery.test.com',
username: 'custom-recovery.test',
password: 'custom-recovery',
inviteCode,
recoveryKey,
@ -231,7 +231,7 @@ describe('account', () => {
{},
{
email: `user${i}@test.com`,
username: `user${i}.test.com`,
username: `user${i}.test`,
password: `password`,
inviteCode,
},
@ -267,7 +267,7 @@ describe('account', () => {
{},
{
email: `matching@test.com`,
username: `matching.test.com`,
username: `matching.test`,
password: `password`,
inviteCode: invite,
},
@ -377,6 +377,7 @@ describe('account', () => {
const user = await db.db
.selectFrom('user')
.innerJoin('user_did', 'user_did.username', 'user.username')
.selectAll()
.where('did', '=', did)
.executeTakeFirst()

@ -17,204 +17,204 @@ export default async (sc: SeedClient) => {
}
const users = [
{ username: 'Silas77', displayName: 'Tanya Denesik' },
{ username: 'Nicolas_Krajcik10', displayName: null },
{ username: 'Lennie.Strosin', displayName: null },
{ username: 'Aliya.Hodkiewicz', displayName: 'Carlton Abernathy IV' },
{ username: 'Jeffrey.Sawayn87', displayName: 'Patrick Sawayn' },
{ username: 'Kaycee66', displayName: null },
{ username: 'Adrienne49', displayName: 'Kim Streich' },
{ username: 'Magnus53', displayName: 'Sally Funk' },
{ username: 'Charles_Spencer', displayName: null },
{ username: 'Elta48', displayName: 'Dr. Lowell DuBuque' },
{ username: 'Tressa_Senger', displayName: null },
{ username: 'Marietta.Zboncak', displayName: null },
{ username: 'Alexander_Hickle', displayName: 'Winifred Harber' },
{ username: 'Rodger.Maggio24', displayName: 'Yolanda VonRueden' },
{ username: 'Janiya48', displayName: 'Miss Terrell Ziemann' },
{ username: 'Cayla_Marquardt39', displayName: 'Rachel Kshlerin' },
{ username: 'Jonathan_Green', displayName: 'Erica Mertz' },
{ username: 'Brycen_Smith', displayName: null },
{ username: 'Leonel.Koch43', displayName: 'Karl Bosco IV' },
{ username: 'Fidel.Rath', displayName: null },
{ username: 'Raleigh_Metz', displayName: null },
{ username: 'Kim41', displayName: null },
{ username: 'Roderick.Dibbert', displayName: null },
{ username: 'Alec_Bergnaum', displayName: 'Cody Berge' },
{ username: 'Sven70', displayName: null },
{ username: 'Ola.OConnell', displayName: null },
{ username: 'Chauncey_Klein', displayName: 'Kelvin Klein' },
{ username: 'Ariel_Krajcik', displayName: null },
{ username: 'Murphy35', displayName: 'Mrs. Clifford Mertz' },
{ username: 'Joshuah.Parker11', displayName: null },
{ username: 'Dewitt.Wunsch', displayName: null },
{ username: 'Kelton.Nitzsche43', displayName: null },
{ username: 'Dock.Mann91', displayName: 'Miss Danielle Weber' },
{ username: 'Herman.Gleichner95', displayName: 'Kelli Schinner III' },
{ username: 'Gerda_Marquardt', displayName: 'Myron Wolf' },
{ username: 'Jamil_Batz', displayName: null },
{ username: 'Hilario84', displayName: null },
{ username: 'Kayli_Bode', displayName: 'Miss Floyd McClure' },
{ username: 'Elouise28', displayName: 'Alberta Fay' },
{ username: 'Leann49', displayName: null },
{ username: 'Javon24', displayName: null },
{ username: 'Polly.Shanahan45', displayName: null },
{ username: 'Rosamond38', displayName: 'Karl Goyette' },
{ username: 'Fredrick.Mueller', displayName: null },
{ username: 'Reina_Runte33', displayName: 'Pablo Schmidt' },
{ username: 'Bianka33', displayName: null },
{ username: 'Carlos6', displayName: null },
{ username: 'Jermain.Smith', displayName: 'Eileen Stroman' },
{ username: 'Gina97', displayName: null },
{ username: 'Kiera97', displayName: null },
{ username: 'Savannah_Botsford', displayName: 'Darnell Kuvalis' },
{ username: 'Lilliana_Waters', displayName: null },
{ username: 'Hailey_Stroman', displayName: 'Elsa Schaden' },
{ username: 'Dortha_Terry', displayName: 'Nicole Bradtke' },
{ username: 'Hank.Powlowski32', displayName: null },
{ username: 'Ervin.Daugherty', displayName: null },
{ username: 'Nannie18', displayName: null },
{ username: 'Gilberto.Watsica65', displayName: 'Ms. Ida Wilderman' },
{ username: 'Kara.Zieme58', displayName: 'Andres Towne' },
{ username: 'Crystal.Boyle', displayName: null },
{ username: 'Tobin63', displayName: 'Alex Johnson' },
{ username: 'Isai.Kunze72', displayName: 'Marion Dickinson' },
{ username: 'Paris.Swift', displayName: null },
{ username: 'Nestor90', displayName: 'Travis Hoppe' },
{ username: 'Aliyah_Flatley12', displayName: 'Loren Krajcik' },
{ username: 'Maiya42', displayName: null },
{ username: 'Dovie33', displayName: null },
{ username: 'Kendra_Ledner80', displayName: 'Sergio Hane' },
{ username: 'Greyson.Tromp3', displayName: null },
{ username: 'Precious_Fay', displayName: null },
{ username: 'Kiana_Schmitt39', displayName: null },
{ username: 'Rhianna_Stamm29', displayName: null },
{ username: 'Tiara_Mohr', displayName: null },
{ username: 'Eleazar.Balistreri70', displayName: 'Gordon Weissnat' },
{ username: 'Bettie.Bogisich96', displayName: null },
{ username: 'Lura.Jacobi55', displayName: null },
{ username: 'Santa_Hermann78', displayName: 'Melissa Johnson' },
{ username: 'Dylan61', displayName: null },
{ username: 'Ryley_Kerluke', displayName: 'Alexander Purdy' },
{ username: 'Moises.Bins8', displayName: null },
{ username: 'Angelita.Schaefer27', displayName: null },
{ username: 'Natasha83', displayName: 'Dean Romaguera' },
{ username: 'Sydni48', displayName: null },
{ username: 'Darrion91', displayName: 'Jeanette Weimann' },
{ username: 'Reynold.Ortiz', displayName: null },
{ username: 'Hassie.Schuppe', displayName: 'Rita Zieme' },
{ username: 'Clark_Stehr8', displayName: 'Sammy Larkin' },
{ username: 'Preston_Harris', displayName: 'Ms. Bradford Thiel' },
{ username: 'Benedict.Schulist', displayName: 'Todd Stark' },
{ username: 'Alden_Wolff22', displayName: null },
{ username: 'Joel.Gulgowski', displayName: null },
{ username: 'Joanie56', displayName: 'Ms. Darin Cole' },
{ username: 'Israel_Hermann0', displayName: 'Wilbur Schuster' },
{ username: 'Tracy56', displayName: null },
{ username: 'Kyle72', displayName: null },
{ username: 'Gunnar_Dare70', displayName: 'Mrs. Angelo Keeling' },
{ username: 'Justus58', displayName: null },
{ username: 'Brooke24', displayName: 'Clint Ward' },
{ username: 'Angela.Morissette', displayName: 'Jim Kertzmann' },
{ username: 'Amy_Bins', displayName: 'Angelina Hills' },
{ username: 'Susanna81', displayName: null },
{ username: 'Jailyn_Hettinger50', displayName: 'Sheldon Ratke' },
{ username: 'Wendell_Hansen54', displayName: null },
{ username: 'Jennyfer.Spinka', displayName: 'Leticia Blick' },
{ username: 'Alexandrea31', displayName: 'Leslie Von' },
{ username: 'Hazle_Davis', displayName: 'Ella Farrell' },
{ username: 'Alta6', displayName: null },
{ username: 'Sherwood4', displayName: 'Dr. Hattie Nienow I' },
{ username: 'Marilie24', displayName: 'Gene Howell' },
{ username: 'Jimmie_Feeney82', displayName: null },
{ username: 'Trisha_OHara', displayName: null },
{ username: 'Jake_Schuster33', displayName: 'Raymond Price' },
{ username: 'Shane_Torphy52', displayName: 'Sadie Carter' },
{ username: 'Nakia_Kuphal8', displayName: null },
{ username: 'Lea_Trantow', displayName: null },
{ username: 'Joel62', displayName: 'Veronica Nitzsche' },
{ username: 'Roosevelt33', displayName: 'Jay Moen' },
{ username: 'Talon_OKeefe85', displayName: null },
{ username: 'Herman_Dare', displayName: 'Eric White' },
{ username: 'Flavio_Fay', displayName: 'John Lindgren' },
{ username: 'Elyse.Prosacco', displayName: null },
{ username: 'Jessyca.Wiegand23', displayName: 'Debra Lockman' },
{ username: 'Ara_Spencer41', displayName: null },
{ username: 'Frederic_Fadel', displayName: null },
{ username: 'Zora_Gerlach', displayName: 'Noel Hansen' },
{ username: 'Spencer4', displayName: 'Marjorie Gorczany' },
{ username: 'Gage_Wilkinson33', displayName: 'Preston Schoen V' },
{ username: 'Kiley_Runolfsson1', displayName: null },
{ username: 'Ramona80', displayName: 'Sylvia Dietrich' },
{ username: 'Rashad97', displayName: null },
{ username: 'Kylie76', displayName: 'Josefina Pfeffer' },
{ username: 'Alisha.Zieme', displayName: null },
{ username: 'Claud79', displayName: null },
{ username: 'Jairo.Kuvalis', displayName: 'Derrick Jacobson' },
{ username: 'Delfina_Emard', displayName: null },
{ username: 'Waino.Gutmann20', displayName: 'Wesley Kemmer' },
{ username: 'Arvid_Hermiston49', displayName: 'Vernon Towne PhD' },
{ username: 'Hans79', displayName: 'Rex Hartmann' },
{ username: 'Karlee.Greenholt40', displayName: null },
{ username: 'Nels.Cummings', displayName: null },
{ username: 'Andrew_Maggio', displayName: null },
{ username: 'Stephany75', displayName: null },
{ username: 'Alba.Lueilwitz', displayName: null },
{ username: 'Fermin47', displayName: null },
{ username: 'Milo_Quitzon3', displayName: null },
{ username: 'Eudora_Dietrich4', displayName: 'Carol Littel' },
{ username: 'Uriel.Witting12', displayName: 'Sophia Schmidt' },
{ username: 'Reuben.Stracke48', displayName: 'Darrell Walker MD' },
{ username: 'Letitia.Sawayn11', displayName: 'Mrs. Sophie Reilly' },
{ username: 'Macy_Schaden', displayName: 'Lindsey Klein' },
{ username: 'Imelda61', displayName: 'Shannon Beier' },
{ username: 'Oswald_Bailey', displayName: 'Angel Mann' },
{ username: 'Pattie.Fisher34', displayName: null },
{ username: 'Loyce95', displayName: 'Claude Tromp' },
{ username: 'Melyna_Zboncak', displayName: null },
{ username: 'Rowan_Parisian', displayName: 'Mr. Veronica Feeney' },
{ username: 'Lois.Blanda20', displayName: 'Todd Rolfson' },
{ username: 'Turner_Balistreri76', displayName: null },
{ username: 'Dee_Hoppe65', displayName: null },
{ username: 'Nikko_Rosenbaum60', displayName: 'Joann Gutmann' },
{ username: 'Cornell.Romaguera53', displayName: null },
{ username: 'Zack3', displayName: null },
{ username: 'Fredrick41', displayName: 'Julius Kreiger' },
{ username: 'Elwyn62', displayName: null },
{ username: 'Isaias.Hirthe37', displayName: 'Louis Cremin' },
{ username: 'Ronaldo36', displayName: null },
{ username: 'Jesse34', displayName: 'Bridget Schulist' },
{ username: 'Darrel.Mills17', displayName: null },
{ username: 'Euna_Mayert92', displayName: 'Grant Lang II' },
{ username: 'Terrell92', displayName: null },
{ username: 'Alyson_Bogisich', displayName: 'Dana MacGyver' },
{ username: 'Nicolas65', displayName: null },
{ username: 'Bernita8', displayName: null },
{ username: 'Gunner23', displayName: 'Maggie DuBuque' },
{ username: 'Phoebe80', displayName: null },
{ username: 'Cory.Cruickshank', displayName: null },
{ username: 'Conor_Price', displayName: 'Ralph Daugherty III' },
{ username: 'Rae91', displayName: null },
{ username: 'Abigale.Cronin', displayName: null },
{ username: 'Aileen.Reilly90', displayName: 'Charles Stanton' },
{ username: 'Adrianna.Hansen6', displayName: 'Elbert Langworth IV' },
{ username: 'Pierre54', displayName: null },
{ username: 'Jaida_Stark62', displayName: 'Justin Stoltenberg MD' },
{ username: 'Wade.Witting', displayName: null },
{ username: 'Yvonne_Predovic5', displayName: 'Gregory Hamill' },
{ username: 'Spencer.DuBuque', displayName: null },
{ username: 'Randi44', displayName: null },
{ username: 'Maye_Grimes', displayName: null },
{ username: 'Margarette.Effertz', displayName: null },
{ username: 'Aimee98', displayName: null },
{ username: 'Jaren_Veum0', displayName: 'Dr. Omar Wolff' },
{ username: 'Ariel_Abbott54', displayName: 'Emanuel Powlowski' },
{ username: 'Mercedes23', displayName: null },
{ username: 'Jarrett_Orn', displayName: null },
{ username: 'Damion88', displayName: null },
{ username: 'Nayeli_Koss73', displayName: 'Johnny Lang' },
{ username: 'Cara.Wiegand69', displayName: null },
{ username: 'Gideon_OHara51', displayName: null },
{ username: 'Carolina_McDermott77', displayName: 'Latoya Windler' },
{ username: 'Danyka90', displayName: 'Hope Kub' },
{ username: 'silas77.test', displayName: 'Tanya Denesik' },
{ username: 'nicolas_krajcik10.test', displayName: null },
{ username: 'lennie-strosin.test', displayName: null },
{ username: 'aliya-hodkiewicz.test', displayName: 'Carlton Abernathy IV' },
{ username: 'jeffrey-sawayn87.test', displayName: 'Patrick Sawayn' },
{ username: 'kaycee66.test', displayName: null },
{ username: 'adrienne49.test', displayName: 'Kim Streich' },
{ username: 'magnus53.test', displayName: 'Sally Funk' },
{ username: 'charles_spencer.test', displayName: null },
{ username: 'elta48.test', displayName: 'Dr. Lowell DuBuque' },
{ username: 'tressa_senger.test', displayName: null },
{ username: 'marietta-zboncak.test', displayName: null },
{ username: 'alexander_hickle.test', displayName: 'Winifred Harber' },
{ username: 'rodger-maggio24.test', displayName: 'Yolanda VonRueden' },
{ username: 'janiya48.test', displayName: 'Miss Terrell Ziemann' },
{ username: 'cayla_marquardt39.test', displayName: 'Rachel Kshlerin' },
{ username: 'jonathan_green.test', displayName: 'Erica Mertz' },
{ username: 'brycen_smith.test', displayName: null },
{ username: 'leonel-koch43.test', displayName: 'Karl Bosco IV' },
{ username: 'fidel-rath.test', displayName: null },
{ username: 'raleigh_metz.test', displayName: null },
{ username: 'kim41.test', displayName: null },
{ username: 'roderick-dibbert.test', displayName: null },
{ username: 'alec_bergnaum.test', displayName: 'Cody Berge' },
{ username: 'sven70.test', displayName: null },
{ username: 'ola-oconnell.test', displayName: null },
{ username: 'chauncey_klein.test', displayName: 'Kelvin Klein' },
{ username: 'ariel_krajcik.test', displayName: null },
{ username: 'murphy35.test', displayName: 'Mrs. Clifford Mertz' },
{ username: 'joshuah-parker11.test', displayName: null },
{ username: 'dewitt-wunsch.test', displayName: null },
{ username: 'kelton-nitzsche43.test', displayName: null },
{ username: 'dock-mann91.test', displayName: 'Miss Danielle Weber' },
{ username: 'herman-gleichner95.test', displayName: 'Kelli Schinner III' },
{ username: 'gerda_marquardt.test', displayName: 'Myron Wolf' },
{ username: 'jamil_batz.test', displayName: null },
{ username: 'hilario84.test', displayName: null },
{ username: 'kayli_bode.test', displayName: 'Miss Floyd McClure' },
{ username: 'elouise28.test', displayName: 'Alberta Fay' },
{ username: 'leann49.test', displayName: null },
{ username: 'javon24.test', displayName: null },
{ username: 'polly-shanahan45.test', displayName: null },
{ username: 'rosamond38.test', displayName: 'Karl Goyette' },
{ username: 'fredrick-mueller.test', displayName: null },
{ username: 'reina_runte33.test', displayName: 'Pablo Schmidt' },
{ username: 'bianka33.test', displayName: null },
{ username: 'carlos6.test', displayName: null },
{ username: 'jermain-smith.test', displayName: 'Eileen Stroman' },
{ username: 'gina97.test', displayName: null },
{ username: 'kiera97.test', displayName: null },
{ username: 'savannah_botsford.test', displayName: 'Darnell Kuvalis' },
{ username: 'lilliana_waters.test', displayName: null },
{ username: 'hailey_stroman.test', displayName: 'Elsa Schaden' },
{ username: 'dortha_terry.test', displayName: 'Nicole Bradtke' },
{ username: 'hank-powlowski32.test', displayName: null },
{ username: 'ervin-daugherty.test', displayName: null },
{ username: 'nannie18.test', displayName: null },
{ username: 'gilberto-watsica65.test', displayName: 'Ms. Ida Wilderman' },
{ username: 'kara-zieme58.test', displayName: 'Andres Towne' },
{ username: 'crystal-boyle.test', displayName: null },
{ username: 'tobin63.test', displayName: 'Alex Johnson' },
{ username: 'isai-kunze72.test', displayName: 'Marion Dickinson' },
{ username: 'paris-swift.test', displayName: null },
{ username: 'nestor90.test', displayName: 'Travis Hoppe' },
{ username: 'aliyah_flatley12.test', displayName: 'Loren Krajcik' },
{ username: 'maiya42.test', displayName: null },
{ username: 'dovie33.test', displayName: null },
{ username: 'kendra_ledner80.test', displayName: 'Sergio Hane' },
{ username: 'greyson-tromp3.test', displayName: null },
{ username: 'precious_fay.test', displayName: null },
{ username: 'kiana_schmitt39.test', displayName: null },
{ username: 'rhianna_stamm29.test', displayName: null },
{ username: 'tiara_mohr.test', displayName: null },
{ username: 'eleazar-balistreri70.test', displayName: 'Gordon Weissnat' },
{ username: 'bettie-bogisich96.test', displayName: null },
{ username: 'lura-jacobi55.test', displayName: null },
{ username: 'santa_hermann78.test', displayName: 'Melissa Johnson' },
{ username: 'dylan61.test', displayName: null },
{ username: 'ryley_kerluke.test', displayName: 'Alexander Purdy' },
{ username: 'moises-bins8.test', displayName: null },
{ username: 'angelita-schaefer27.test', displayName: null },
{ username: 'natasha83.test', displayName: 'Dean Romaguera' },
{ username: 'sydni48.test', displayName: null },
{ username: 'darrion91.test', displayName: 'Jeanette Weimann' },
{ username: 'reynold-ortiz.test', displayName: null },
{ username: 'hassie-schuppe.test', displayName: 'Rita Zieme' },
{ username: 'clark_stehr8.test', displayName: 'Sammy Larkin' },
{ username: 'preston_harris.test', displayName: 'Ms. Bradford Thiel' },
{ username: 'benedict-schulist.test', displayName: 'Todd Stark' },
{ username: 'alden_wolff22.test', displayName: null },
{ username: 'joel-gulgowski.test', displayName: null },
{ username: 'joanie56.test', displayName: 'Ms. Darin Cole' },
{ username: 'israel_hermann0.test', displayName: 'Wilbur Schuster' },
{ username: 'tracy56.test', displayName: null },
{ username: 'kyle72.test', displayName: null },
{ username: 'gunnar_dare70.test', displayName: 'Mrs. Angelo Keeling' },
{ username: 'justus58.test', displayName: null },
{ username: 'brooke24.test', displayName: 'Clint Ward' },
{ username: 'angela-morissette.test', displayName: 'Jim Kertzmann' },
{ username: 'amy_bins.test', displayName: 'Angelina Hills' },
{ username: 'susanna81.test', displayName: null },
{ username: 'jailyn_hettinger50.test', displayName: 'Sheldon Ratke' },
{ username: 'wendell_hansen54.test', displayName: null },
{ username: 'jennyfer-spinka.test', displayName: 'Leticia Blick' },
{ username: 'alexandrea31.test', displayName: 'Leslie Von' },
{ username: 'hazle_davis.test', displayName: 'Ella Farrell' },
{ username: 'alta6.test', displayName: null },
{ username: 'sherwood4.test', displayName: 'Dr. Hattie Nienow I' },
{ username: 'marilie24.test', displayName: 'Gene Howell' },
{ username: 'jimmie_feeney82.test', displayName: null },
{ username: 'trisha_ohara.test', displayName: null },
{ username: 'jake_schuster33.test', displayName: 'Raymond Price' },
{ username: 'shane_torphy52.test', displayName: 'Sadie Carter' },
{ username: 'nakia_kuphal8.test', displayName: null },
{ username: 'lea_trantow.test', displayName: null },
{ username: 'joel62.test', displayName: 'Veronica Nitzsche' },
{ username: 'roosevelt33.test', displayName: 'Jay Moen' },
{ username: 'talon_okeefe85.test', displayName: null },
{ username: 'herman_dare.test', displayName: 'Eric White' },
{ username: 'flavio_fay.test', displayName: 'John Lindgren' },
{ username: 'elyse-prosacco.test', displayName: null },
{ username: 'jessyca-wiegand23.test', displayName: 'Debra Lockman' },
{ username: 'ara_spencer41.test', displayName: null },
{ username: 'frederic_fadel.test', displayName: null },
{ username: 'zora_gerlach.test', displayName: 'Noel Hansen' },
{ username: 'spencer4.test', displayName: 'Marjorie Gorczany' },
{ username: 'gage_wilkinson33.test', displayName: 'Preston Schoen V' },
{ username: 'kiley_runolfsson1.test', displayName: null },
{ username: 'ramona80.test', displayName: 'Sylvia Dietrich' },
{ username: 'rashad97.test', displayName: null },
{ username: 'kylie76.test', displayName: 'Josefina Pfeffer' },
{ username: 'alisha-zieme.test', displayName: null },
{ username: 'claud79.test', displayName: null },
{ username: 'jairo-kuvalis.test', displayName: 'Derrick Jacobson' },
{ username: 'delfina_emard.test', displayName: null },
{ username: 'waino-gutmann20.test', displayName: 'Wesley Kemmer' },
{ username: 'arvid_hermiston49.test', displayName: 'Vernon Towne PhD' },
{ username: 'hans79.test', displayName: 'Rex Hartmann' },
{ username: 'karlee-greenholt40.test', displayName: null },
{ username: 'nels-cummings.test', displayName: null },
{ username: 'andrew_maggio.test', displayName: null },
{ username: 'stephany75.test', displayName: null },
{ username: 'alba-lueilwitz.test', displayName: null },
{ username: 'fermin47.test', displayName: null },
{ username: 'milo_quitzon3.test', displayName: null },
{ username: 'eudora_dietrich4.test', displayName: 'Carol Littel' },
{ username: 'uriel-witting12.test', displayName: 'Sophia Schmidt' },
{ username: 'reuben-stracke48.test', displayName: 'Darrell Walker MD' },
{ username: 'letitia-sawayn11.test', displayName: 'Mrs. Sophie Reilly' },
{ username: 'macy_schaden.test', displayName: 'Lindsey Klein' },
{ username: 'imelda61.test', displayName: 'Shannon Beier' },
{ username: 'oswald_bailey.test', displayName: 'Angel Mann' },
{ username: 'pattie-fisher34.test', displayName: null },
{ username: 'loyce95.test', displayName: 'Claude Tromp' },
{ username: 'melyna_zboncak.test', displayName: null },
{ username: 'rowan_parisian.test', displayName: 'Mr. Veronica Feeney' },
{ username: 'lois-blanda20.test', displayName: 'Todd Rolfson' },
{ username: 'turner_balistreri76.test', displayName: null },
{ username: 'dee_hoppe65.test', displayName: null },
{ username: 'nikko_rosenbaum60.test', displayName: 'Joann Gutmann' },
{ username: 'cornell-romaguera53.test', displayName: null },
{ username: 'zack3.test', displayName: null },
{ username: 'fredrick41.test', displayName: 'Julius Kreiger' },
{ username: 'elwyn62.test', displayName: null },
{ username: 'isaias-hirthe37.test', displayName: 'Louis Cremin' },
{ username: 'ronaldo36.test', displayName: null },
{ username: 'jesse34.test', displayName: 'Bridget Schulist' },
{ username: 'darrel-mills17.test', displayName: null },
{ username: 'euna_mayert92.test', displayName: 'Grant Lang II' },
{ username: 'terrell92.test', displayName: null },
{ username: 'alyson_bogisich.test', displayName: 'Dana MacGyver' },
{ username: 'nicolas65.test', displayName: null },
{ username: 'bernita8.test', displayName: null },
{ username: 'gunner23.test', displayName: 'Maggie DuBuque' },
{ username: 'phoebe80.test', displayName: null },
{ username: 'cory-cruickshank.test', displayName: null },
{ username: 'conor_price.test', displayName: 'Ralph Daugherty III' },
{ username: 'rae91.test', displayName: null },
{ username: 'abigale-cronin.test', displayName: null },
{ username: 'aileen-reilly90.test', displayName: 'Charles Stanton' },
{ username: 'adrianna-hansen6.test', displayName: 'Elbert Langworth IV' },
{ username: 'pierre54.test', displayName: null },
{ username: 'jaida_stark62.test', displayName: 'Justin Stoltenberg MD' },
{ username: 'wade-witting.test', displayName: null },
{ username: 'yvonne_predovic5.test', displayName: 'Gregory Hamill' },
{ username: 'spencer-dubuque.test', displayName: null },
{ username: 'randi44.test', displayName: null },
{ username: 'maye_grimes.test', displayName: null },
{ username: 'margarette-effertz.test', displayName: null },
{ username: 'aimee98.test', displayName: null },
{ username: 'jaren_veum0.test', displayName: 'Dr. Omar Wolff' },
{ username: 'ariel_abbott54.test', displayName: 'Emanuel Powlowski' },
{ username: 'mercedes23.test', displayName: null },
{ username: 'jarrett_orn.test', displayName: null },
{ username: 'damion88.test', displayName: null },
{ username: 'nayeli_koss73.test', displayName: 'Johnny Lang' },
{ username: 'cara-wiegand69.test', displayName: null },
{ username: 'gideon_ohara51.test', displayName: null },
{ username: 'carolina_mcdermott77.test', displayName: 'Latoya Windler' },
{ username: 'danyka90.test', displayName: 'Hope Kub' },
]

@ -38,30 +38,30 @@ describe('pds user search views', () => {
const names = result.data.users.map((u) => u.name)
const shouldContain = [
'Cara.Wiegand69',
'Eudora_Dietrich4', // Carol Littel
'Shane_Torphy52', // Sadie Carter
'Aliya.Hodkiewicz', // Carlton Abernathy IV
'Carlos6',
'Carolina_McDermott77',
'cara-wiegand69.test',
'eudora_dietrich4.test', // Carol Littel
'shane_torphy52.test', //Sadie Carter
'aliya-hodkiewicz.test', // Carlton Abernathy IV
'carlos6.test',
'carolina_mcdermott77.test',
]
shouldContain.forEach((name) => expect(names).toContain(name))
if (locals.get(app).db.dialect === 'pg') {
expect(names).toContain('Cayla_Marquardt39') // Fuzzy match supported by postgres
expect(names).toContain('cayla_marquardt39.test') // Fuzzy match supported by postgres
} else {
expect(names).not.toContain('Cayla_Marquardt39')
expect(names).not.toContain('cayla_marquardt39.test')
}
const shouldNotContain = [
'Sven70',
'Hilario84',
'Santa_Hermann78',
'Dylan61',
'Preston_Harris',
'Loyce95',
'Melyna_Zboncak',
'sven70.test',
'hilario84.test',
'santa_hermann78.test',
'dylan61.test',
'preston_harris.test',
'loyce95.test',
'melyna_zboncak.test',
]
shouldNotContain.forEach((name) => expect(names).not.toContain(name))
@ -111,30 +111,30 @@ describe('pds user search views', () => {
const names = result.data.users.map((u) => u.name)
const shouldContain = [
'Cara.Wiegand69',
'Eudora_Dietrich4', // Carol Littel
'Shane_Torphy52', // Sadie Carter
'Aliya.Hodkiewicz', // Carlton Abernathy IV
'Carlos6',
'Carolina_McDermott77',
'cara-wiegand69.test',
'eudora_dietrich4.test', // Carol Littel
'shane_torphy52.test', //Sadie Carter
'aliya-hodkiewicz.test', // Carlton Abernathy IV
'carlos6.test',
'carolina_mcdermott77.test',
]
shouldContain.forEach((name) => expect(names).toContain(name))
if (locals.get(app).db.dialect === 'pg') {
expect(names).toContain('Cayla_Marquardt39') // Fuzzy match supported by postgres
expect(names).toContain('cayla_marquardt39.test') // Fuzzy match supported by postgres
} else {
expect(names).not.toContain('Cayla_Marquardt39')
expect(names).not.toContain('cayla_marquardt39.test')
}
const shouldNotContain = [
'Sven70',
'Hilario84',
'Santa_Hermann78',
'Dylan61',
'Preston_Harris',
'Loyce95',
'Melyna_Zboncak',
'sven70.test',
'hilario84.test',
'santa_hermann78.test',
'dylan61.test',
'preston_harris.test',
'loyce95.test',
'melyna_zboncak.test',
]
shouldNotContain.forEach((name) => expect(names).not.toContain(name))
@ -202,36 +202,36 @@ describe('pds user search views', () => {
const snapTypeaheadPg = [
{
did: 'user(0)',
name: 'Cara.Wiegand69',
name: 'cara-wiegand69.test',
},
{
did: 'user(1)',
displayName: 'Carol Littel',
name: 'Eudora_Dietrich4',
name: 'eudora_dietrich4.test',
},
{
did: 'user(2)',
displayName: 'Sadie Carter',
name: 'Shane_Torphy52',
name: 'shane_torphy52.test',
},
{
did: 'user(3)',
displayName: 'Carlton Abernathy IV',
name: 'Aliya.Hodkiewicz',
name: 'aliya-hodkiewicz.test',
},
{
did: 'user(4)',
name: 'Carlos6',
name: 'carlos6.test',
},
{
did: 'user(5)',
displayName: 'Latoya Windler',
name: 'Carolina_McDermott77',
name: 'carolina_mcdermott77.test',
},
{
did: 'user(6)',
displayName: 'Rachel Kshlerin',
name: 'Cayla_Marquardt39',
name: 'cayla_marquardt39.test',
},
]
@ -239,131 +239,114 @@ const snapTypeaheadSqlite = [
{
did: 'user(0)',
displayName: 'Carlton Abernathy IV',
name: 'Aliya.Hodkiewicz',
name: 'aliya-hodkiewicz.test',
},
{
did: 'user(1)',
name: 'Cara.Wiegand69',
name: 'cara-wiegand69.test',
},
{
did: 'user(2)',
name: 'Carlos6',
name: 'carlos6.test',
},
{
did: 'user(3)',
displayName: 'Latoya Windler',
name: 'Carolina_McDermott77',
name: 'carolina_mcdermott77.test',
},
{
did: 'user(4)',
displayName: 'Carol Littel',
name: 'Eudora_Dietrich4',
name: 'eudora_dietrich4.test',
},
{
did: 'user(5)',
displayName: 'Sadie Carter',
name: 'Shane_Torphy52',
name: 'shane_torphy52.test',
},
]
const snapSearchPg = [
{
createdAt: '1970-01-01T00:00:00.000Z',
did: 'user(0)',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Cara.Wiegand69',
name: 'cara-wiegand69.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
description: '',
did: 'user(1)',
displayName: 'Carol Littel',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Eudora_Dietrich4',
name: 'eudora_dietrich4.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
description: '',
did: 'user(2)',
displayName: 'Sadie Carter',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Shane_Torphy52',
name: 'shane_torphy52.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
description: '',
did: 'user(3)',
displayName: 'Carlton Abernathy IV',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Aliya.Hodkiewicz',
name: 'aliya-hodkiewicz.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
did: 'user(4)',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Carlos6',
name: 'carlos6.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
description: '',
did: 'user(5)',
displayName: 'Latoya Windler',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Carolina_McDermott77',
name: 'carolina_mcdermott77.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
description: '',
did: 'user(6)',
displayName: 'Rachel Kshlerin',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Cayla_Marquardt39',
name: 'cayla_marquardt39.test',
},
]
const snapSearchSqlite = [
{
createdAt: '1970-01-01T00:00:00.000Z',
description: '',
did: 'user(0)',
displayName: 'Carlton Abernathy IV',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Aliya.Hodkiewicz',
name: 'aliya-hodkiewicz.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
did: 'user(1)',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Cara.Wiegand69',
name: 'cara-wiegand69.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
did: 'user(2)',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Carlos6',
name: 'carlos6.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
description: '',
did: 'user(3)',
displayName: 'Latoya Windler',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Carolina_McDermott77',
name: 'carolina_mcdermott77.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
description: '',
did: 'user(4)',
displayName: 'Carol Littel',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Eudora_Dietrich4',
name: 'eudora_dietrich4.test',
},
{
createdAt: '1970-01-01T00:00:00.000Z',
description: '',
did: 'user(5)',
displayName: 'Sadie Carter',
indexedAt: '1970-01-01T00:00:00.000Z',
name: 'Shane_Torphy52',
name: 'shane_torphy52.test',
},
]