diff --git a/CHANGELOG.md b/CHANGELOG.md index 566ec4e..7cd47ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +- Expose `createOrupdateUsers` callback helper functions + `basicCreateOrUpdateUser` and `callAfterUserCreatedOrUpdated`. This makes + writing custom `createOrUpdateUsers` callbacks simpler. + ## 0.0.78 - Add support for diff --git a/docs/package-lock.json b/docs/package-lock.json index cd0fce6..062dcd4 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "@hookform/resolvers": "^3.9.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", "class-variance-authority": "^0.7.1", @@ -142,15 +141,6 @@ "react-dom": "^16 || ^17 || ^18" } }, - "node_modules/@hookform/resolvers": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.1.tgz", - "integrity": "sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==", - "license": "MIT", - "peerDependencies": { - "react-hook-form": "^7.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", diff --git a/docs/pages/advanced.mdx b/docs/pages/advanced.mdx index e909d18..f306d53 100644 --- a/docs/pages/advanced.mdx +++ b/docs/pages/advanced.mdx @@ -77,6 +77,7 @@ providing the import GitHub from "@auth/core/providers/github"; import Password from "@convex-dev/auth/providers/Password"; import { MutationCtx } from "./_generated/server"; +import { basicCreateOrUpdateUser, callAfterUserCreatedOrUpdated } from "@convex-dev/auth/server"; export const { auth, signIn, signOut, store, isAuthenticated } = { providers: [GitHub, Password], @@ -84,19 +85,25 @@ export const { auth, signIn, signOut, store, isAuthenticated } = { // `args.type` is one of "oauth" | "email" | "phone" | "credentials" | "verification" // `args.provider` is the currently used provider config async createOrUpdateUser(ctx: MutationCtx, args) { - if (args.existingUserId) { - // Optionally merge updated fields into the existing user object here - return args.existingUserId; - } - - // Implement your own account linking logic: - const existingUser = await findUserByEmail(ctx, args.profile.email); - if (existingUser) return existingUser._id; - - // Implement your own user creation: - return ctx.db.insert("users", { - /* ... */ - }); + // This implementation matches the default, modify it to implement custom logic. + + // Replace this logic with custom account linking and custom + // users table insert/update if desired. + const { userId, existingUserId: existingOrLinkedUserId } = + await basicCreateOrUpdateUser(ctx, { + existingUserId, + ...args, + }); + + // Include this or else any passed + // `config.callbacks.afterUserCreatedOrUpdated` won't run. + await callAfterUserCreatedOrUpdated( + ctx, + userId, + existingOrLinkedUserId, + args, + config, + ); }, }, }; diff --git a/docs/pages/api_reference/nextjs/server.mdx b/docs/pages/api_reference/nextjs/server.mdx index 839aaa7..6f21edb 100644 --- a/docs/pages/api_reference/nextjs/server.mdx +++ b/docs/pages/api_reference/nextjs/server.mdx @@ -457,8 +457,8 @@ Returns a function that accepts a `Request` object and returns whether the reque predefined routes that can be passed in as the first argument. You can use glob patterns to match multiple routes or a function to match against the request object. -Path patterns and regular expressions are supported, for example: `['/foo', '/bar(.*)'] or `[/^/foo/.*$/]` -For more information, see: https://github.com/pillarjs/path-to-regexp +Path patterns and limited regular expressions are supported. +For more information, see: https://www.npmjs.com/package/path-to-regexp/v/6.3.0

Parameters

diff --git a/docs/pages/api_reference/server.mdx b/docs/pages/api_reference/server.mdx index d219b32..cedc6ee 100644 --- a/docs/pages/api_reference/server.mdx +++ b/docs/pages/api_reference/server.mdx @@ -1341,7 +1341,24 @@ config. -`args.shouldLink`? +`args.shouldLinkViaEmail`? + + + + +`boolean` + + + + +Whether to link the account via email. + + + + + + +`args.shouldLinkViaPhone`? @@ -1351,7 +1368,7 @@ config. -The `shouldLink` argument passed to `createAccount`. +Whether to link the account via phone. @@ -1514,7 +1531,24 @@ config. -`args.shouldLink`? +`args.shouldLinkViaEmail`? + + + + +`boolean` + + + + +Whether to link the account via email. + + + + + + +`args.shouldLinkViaPhone`? @@ -1524,7 +1558,7 @@ config. -The `shouldLink` argument passed to `createAccount`. +Whether to link the account via phone. @@ -1549,7 +1583,7 @@ service.

Defined in

-[src/server/types.ts:232](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L232) +[src/server/types.ts:240](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L240) *** @@ -1916,7 +1950,7 @@ The values passed to the `signIn` function.
Defined in
-[src/server/types.ts:256](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L256) +[src/server/types.ts:264](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L264) *** @@ -1927,7 +1961,7 @@ Configurable options for an email provider config.

Defined in

-[src/server/types.ts:268](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L268) +[src/server/types.ts:276](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L276) *** @@ -1944,14 +1978,14 @@ phone number instead of the email address.
Defined in
-[src/server/types.ts:279](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L279) +[src/server/types.ts:287](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L287) #### type
Defined in
-[src/server/types.ts:280](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L280) +[src/server/types.ts:288](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L288) #### maxAge @@ -1961,7 +1995,7 @@ Token expiration in seconds.
Defined in
-[src/server/types.ts:284](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L284) +[src/server/types.ts:292](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L292) #### sendVerificationRequest() @@ -2067,7 +2101,7 @@ Send the phone number verification request.
Defined in
-[src/server/types.ts:288](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L288) +[src/server/types.ts:296](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L296) #### apiKey? @@ -2077,7 +2111,7 @@ Defaults to `process.env.AUTH__KEY`.
Defined in
-[src/server/types.ts:301](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L301) +[src/server/types.ts:309](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L309) #### generateVerificationToken()? @@ -2088,7 +2122,7 @@ Defaults to `process.env.AUTH__KEY`.
Defined in
-[src/server/types.ts:310](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L310) +[src/server/types.ts:318](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L318) #### normalizeIdentifier()? @@ -2128,7 +2162,7 @@ The phone number used in `sendVerificationRequest`.
Defined in
-[src/server/types.ts:316](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L316) +[src/server/types.ts:324](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L324) #### authorize()? @@ -2189,14 +2223,14 @@ The values passed to the `signIn` function.
Defined in
-[src/server/types.ts:324](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L324) +[src/server/types.ts:332](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L332) #### options
Defined in
-[src/server/types.ts:331](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L331) +[src/server/types.ts:339](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L339) *** @@ -2207,7 +2241,7 @@ Configurable options for a phone provider config.

Defined in

-[src/server/types.ts:337](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L337) +[src/server/types.ts:345](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L345) *** @@ -2227,7 +2261,7 @@ Similar to Auth.js Credentials config.

Defined in

-[src/server/types.ts:344](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L344) +[src/server/types.ts:352](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L352) *** @@ -2247,7 +2281,7 @@ the config passed to `convexAuth`.

Defined in

-[src/server/types.ts:353](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L353) +[src/server/types.ts:361](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L361) *** @@ -2270,7 +2304,7 @@ See [ConvexAuthConfig](server.mdx#convexauthconfig)

Defined in

-[src/server/types.ts:364](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L364) +[src/server/types.ts:372](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L372) *** @@ -2281,4 +2315,4 @@ Materialized Auth.js provider config.

Defined in

-[src/server/types.ts:372](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L372) +[src/server/types.ts:380](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L380) diff --git a/src/server/implementation/index.ts b/src/server/implementation/index.ts index 20a2857..0d0521c 100644 --- a/src/server/implementation/index.ts +++ b/src/server/implementation/index.ts @@ -49,9 +49,16 @@ import { import { signInImpl } from "./signIn.js"; import { redirectAbsoluteUrl, setURLSearchParam } from "./redirects.js"; import { getAuthorizationUrl } from "../oauth/authorizationUrl.js"; -import { defaultCookiesOptions, oAuthConfigToInternalProvider } from "../oauth/convexAuth.js"; +import { + defaultCookiesOptions, + oAuthConfigToInternalProvider, +} from "../oauth/convexAuth.js"; import { handleOAuth } from "../oauth/callback.js"; export { getAuthSessionId } from "./sessions.js"; +export { + basicCreateOrUpdateUser, + callAfterUserCreatedOrUpdated, +} from "./users.js"; /** * @internal @@ -240,12 +247,10 @@ export function convexAuth(config_: ConvexAuthConfig) { providerId, ) as OAuthConfig; const { redirect, cookies, signature } = - await getAuthorizationUrl( - { - provider: await oAuthConfigToInternalProvider(provider), - cookies: defaultCookiesOptions(providerId), - }, - ); + await getAuthorizationUrl({ + provider: await oAuthConfigToInternalProvider(provider), + cookies: defaultCookiesOptions(providerId), + }); await callVerifierSignature(ctx, { verifier, @@ -295,10 +300,11 @@ export function convexAuth(config_: ConvexAuthConfig) { }); const params = url.searchParams; - + // Handle OAuth providers that use formData (such as Apple) if ( - request.headers.get("Content-Type") === "application/x-www-form-urlencoded" + request.headers.get("Content-Type") === + "application/x-www-form-urlencoded" ) { const formData = await request.formData(); for (const [key, value] of formData.entries()) { @@ -441,15 +447,18 @@ export function convexAuth(config_: ConvexAuthConfig) { return storeImpl(ctx, args, getProviderOrThrow, config); }, }), - + /** * Utility function for frameworks to use to get the current auth state * based on credentials that they've supplied separately. */ - isAuthenticated: queryGeneric({args: {}, handler: async (ctx, _args): Promise => { - const ident = await ctx.auth.getUserIdentity(); - return ident !== null; - }}), + isAuthenticated: queryGeneric({ + args: {}, + handler: async (ctx, _args): Promise => { + const ident = await ctx.auth.getUserIdentity(); + return ident !== null; + }, + }), }; } diff --git a/src/server/implementation/users.ts b/src/server/implementation/users.ts index a91da71..3bdd6d2 100644 --- a/src/server/implementation/users.ts +++ b/src/server/implementation/users.ts @@ -1,5 +1,5 @@ import { GenericId } from "convex/values"; -import { AuthDataModel, Doc, MutationCtx, QueryCtx } from "./types.js"; +import { Doc, MutationCtx, QueryCtx } from "./types.js"; import { AuthProviderMaterializedConfig, ConvexAuthConfig } from "../types.js"; import { LOG_LEVELS, logWithLevel } from "./utils.js"; import { @@ -38,7 +38,7 @@ export async function upsertUserAndAccount( userId: GenericId<"users">; accountId: GenericId<"authAccounts">; }> { - const userId = await defaultCreateOrUpdateUser( + const userId = await callCreateOrUpdateUser( ctx, sessionId, "existingAccount" in account ? account.existingAccount : null, @@ -49,14 +49,14 @@ export async function upsertUserAndAccount( return { userId, accountId }; } -async function defaultCreateOrUpdateUser( +async function callCreateOrUpdateUser( ctx: MutationCtx, existingSessionId: GenericId<"authSessions"> | null, existingAccount: Doc<"authAccounts"> | null, args: CreateOrUpdateUserArgs, config: ConvexAuthConfig, ) { - logWithLevel(LOG_LEVELS.DEBUG, "defaultCreateOrUpdateUser args:", { + logWithLevel(LOG_LEVELS.DEBUG, "callCreateOrUpdateUser args:", { existingAccountId: existingAccount?._id, existingSessionId, args, @@ -102,7 +102,7 @@ export async function basicCreateOrUpdateUser( return { userId, existingUserId: existingOrLinkedUserId }; } -async function callAfterUserCreatedOrUpdated( +export async function callAfterUserCreatedOrUpdated( ctx: MutationCtx, userId: GenericId<"users">, existingUserId: GenericId<"users"> | null, diff --git a/src/server/index.ts b/src/server/index.ts index 3aadb8a..a355b23 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -20,6 +20,8 @@ export { signInViaProvider, invalidateSessions, modifyAccountCredentials, + callAfterUserCreatedOrUpdated, + basicCreateOrUpdateUser, Tokens, Doc, } from "./implementation/index.js"; diff --git a/test/convex/schema.ts b/test/convex/schema.ts index a055ad0..9ce5d4c 100644 --- a/test/convex/schema.ts +++ b/test/convex/schema.ts @@ -8,8 +8,10 @@ export default defineSchema({ name: v.optional(v.string()), image: v.optional(v.string()), email: v.optional(v.string()), + emailVerified: v.optional(v.boolean()), emailVerificationTime: v.optional(v.number()), phone: v.optional(v.string()), + phoneVerified: v.optional(v.boolean()), phoneVerificationTime: v.optional(v.number()), isAnonymous: v.optional(v.boolean()), // Custom field.