Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions src/server/implementation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
GenericActionCtxWithAuthConfig,
} from "../types.js";
import { requireEnv } from "../utils.js";
import { ActionCtx, MutationCtx, Tokens } from "./types.js";
import { ActionCtx, MutationCtx, Tokens, SessionInfoWithTokens } from "./types.js";
export { authTables, Doc, Tokens } from "./types.js";
import {
LOG_LEVELS,
Expand All @@ -40,6 +40,7 @@ import {
callInvalidateSessions,
callModifyAccount,
callRetreiveAccountWithCredentials,
callSignIn,
callSignOut,
callUserOAuth,
callVerifierSignature,
Expand All @@ -54,6 +55,7 @@ import {
oAuthConfigToInternalProvider,
} from "../oauth/convexAuth.js";
import { handleOAuth } from "../oauth/callback.js";
import { requireSiteUrl } from "./runtimeEnv.js";
export { getAuthSessionId } from "./sessions.js";

/**
Expand Down Expand Up @@ -197,13 +199,12 @@ export function convexAuth(config_: ConvexAuthConfig) {
path: "/.well-known/openid-configuration",
method: "GET",
handler: httpActionGeneric(async () => {
const siteUrl = requireSiteUrl(config);
return new Response(
JSON.stringify({
issuer: requireEnv("CONVEX_SITE_URL"),
jwks_uri:
requireEnv("CONVEX_SITE_URL") + "/.well-known/jwks.json",
authorization_endpoint:
requireEnv("CONVEX_SITE_URL") + "/oauth/authorize",
issuer: siteUrl,
jwks_uri: siteUrl + "/.well-known/jwks.json",
authorization_endpoint: siteUrl + "/oauth/authorize",
}),
{
status: 200,
Expand Down Expand Up @@ -251,10 +252,11 @@ export function convexAuth(config_: ConvexAuthConfig) {
const provider = getProviderOrThrow(
providerId,
) as OAuthConfig<any>;
const siteUrl = requireSiteUrl(config);
const { redirect, cookies, signature } =
await getAuthorizationUrl({
provider: await oAuthConfigToInternalProvider(provider),
cookies: defaultCookiesOptions(providerId),
cookies: defaultCookiesOptions(providerId, siteUrl),
});

await callVerifierSignature(ctx, {
Expand Down Expand Up @@ -319,13 +321,15 @@ export function convexAuth(config_: ConvexAuthConfig) {
}
}

const siteUrl = requireSiteUrl(config);

try {
const { profile, tokens, signature } = await handleOAuth(
Object.fromEntries(params.entries()),
cookies,
{
provider: await oAuthConfigToInternalProvider(provider),
cookies: defaultCookiesOptions(provider.id),
cookies: defaultCookiesOptions(provider.id, siteUrl),
},
);

Expand Down Expand Up @@ -603,6 +607,35 @@ export async function retrieveAccount<
return result;
}

/**
* Issue an access token and refresh token for a user by creating
* or reusing a session.
*/
export async function issueTokens<
DataModel extends GenericDataModel = GenericDataModel,
>(
ctx: GenericActionCtx<DataModel>,
args: {
userId: GenericId<"users">;
sessionId?: GenericId<"authSessions">;
},
): Promise<SessionInfoWithTokens> {
const actionCtx = ctx as unknown as ActionCtx;
const result = await callSignIn(actionCtx, {
userId: args.userId,
sessionId: args.sessionId,
generateTokens: true,
});
if (result.tokens === null) {
throw new Error("Failed to generate tokens for session");
}
return {
userId: result.userId,
sessionId: result.sessionId,
tokens: result.tokens,
};
}

/**
* Use this function to modify the account credentials
* from a [`ConvexCredentials`](https://labs.convex.dev/auth/api_reference/providers/ConvexCredentials)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConvexCredentialsConfig } from "../../types.js";
import { upsertUserAndAccount } from "../users.js";
import { getAuthSessionId } from "../sessions.js";
import { LOG_LEVELS, logWithLevel, maybeRedact } from "../utils.js";
import { collectRuntimeEnv } from "../runtimeEnv.js";

export const createAccountFromCredentialsArgs = v.object({
provider: v.string(),
Expand Down Expand Up @@ -94,5 +95,6 @@ export const callCreateAccountFromCredentials = async (
type: "createAccountFromCredentials",
...args,
},
env: collectRuntimeEnv(),
});
};
2 changes: 2 additions & 0 deletions src/server/implementation/mutations/createVerificationCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EmailConfig, PhoneConfig } from "../../types.js";
import { getAccountOrThrow, upsertUserAndAccount } from "../users.js";
import { getAuthSessionId } from "../sessions.js";
import { LOG_LEVELS, logWithLevel, sha256 } from "../utils.js";
import { collectRuntimeEnv } from "../runtimeEnv.js";

export const createVerificationCodeArgs = v.object({
accountId: v.optional(v.id("authAccounts")),
Expand Down Expand Up @@ -80,6 +81,7 @@ export const callCreateVerificationCode = async (
type: "createVerificationCode",
...args,
},
env: collectRuntimeEnv(),
});
};

Expand Down
39 changes: 36 additions & 3 deletions src/server/implementation/mutations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ import {
import * as Provider from "../provider.js";
import { verifierImpl } from "./verifier.js";
import { LOG_LEVELS, logWithLevel } from "../utils.js";
import {
AuthRuntimeEnv,
mergeRuntimeEnv,
} from "../runtimeEnv.js";
export { callInvalidateSessions } from "./invalidateSessions.js";
export { callModifyAccount } from "./modifyAccount.js";
export { callRetreiveAccountWithCredentials } from "./retrieveAccountWithCredentials.js";
Expand All @@ -45,6 +49,11 @@ export { callRefreshSession } from "./refreshSession.js";
export { callSignOut } from "./signOut.js";
export { callSignIn } from "./signIn.js";

const runtimeEnvArgs = v.object({
siteUrl: v.optional(v.string()),
customAuthSiteUrl: v.optional(v.string()),
});

export const storeArgs = v.object({
args: v.union(
v.object({
Expand Down Expand Up @@ -94,6 +103,7 @@ export const storeArgs = v.object({
...invalidateSessionsArgs.fields,
}),
),
env: v.optional(runtimeEnvArgs),
});

export const storeImpl = async (
Expand All @@ -103,19 +113,42 @@ export const storeImpl = async (
config: Provider.Config,
) => {
const args = fnArgs.args;
const runtimeEnv: AuthRuntimeEnv = mergeRuntimeEnv(
config as any,
fnArgs.env,
);
if (process.env.AUTH_LOG_LEVEL === "DEBUG") {
console.debug("storeImpl runtimeEnv", {
pid: process.pid,
type: args.type,
runtimeEnv,
});
}
logWithLevel(LOG_LEVELS.INFO, `\`auth:store\` type: ${args.type}`);
switch (args.type) {
case "signIn": {
return signInImpl(ctx, args, config);
return signInImpl(ctx, args, config, runtimeEnv);
}
case "signOut": {
return signOutImpl(ctx);
}
case "refreshSession": {
return refreshSessionImpl(ctx, args, getProviderOrThrow, config);
return refreshSessionImpl(
ctx,
args,
getProviderOrThrow,
config,
runtimeEnv,
);
}
case "verifyCodeAndSignIn": {
return verifyCodeAndSignInImpl(ctx, args, getProviderOrThrow, config);
return verifyCodeAndSignInImpl(
ctx,
args,
getProviderOrThrow,
config,
runtimeEnv,
);
}
case "verifier": {
return verifierImpl(ctx);
Expand Down
2 changes: 2 additions & 0 deletions src/server/implementation/mutations/invalidateSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Infer, v } from "convex/values";
import { deleteSession } from "../sessions.js";
import { ActionCtx, MutationCtx } from "../types.js";
import { LOG_LEVELS, logWithLevel } from "../utils.js";
import { collectRuntimeEnv } from "../runtimeEnv.js";

export const invalidateSessionsArgs = v.object({
userId: v.id("users"),
Expand All @@ -17,6 +18,7 @@ export const callInvalidateSessions = async (
type: "invalidateSessions",
...args,
},
env: collectRuntimeEnv(),
});
};

Expand Down
2 changes: 2 additions & 0 deletions src/server/implementation/mutations/modifyAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Infer, v } from "convex/values";
import { ActionCtx, MutationCtx } from "../types.js";
import { GetProviderOrThrowFunc, hash } from "../provider.js";
import { LOG_LEVELS, logWithLevel, maybeRedact } from "../utils.js";
import { collectRuntimeEnv } from "../runtimeEnv.js";

export const modifyAccountArgs = v.object({
provider: v.string(),
Expand Down Expand Up @@ -47,5 +48,6 @@ export const callModifyAccount = async (
type: "modifyAccount",
...args,
},
env: collectRuntimeEnv(),
});
};
12 changes: 9 additions & 3 deletions src/server/implementation/mutations/refreshSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import {
refreshTokenIfValid,
} from "../refreshTokens.js";
import { generateTokensForSession } from "../sessions.js";
import {
AuthRuntimeEnv,
collectRuntimeEnv,
} from "../runtimeEnv.js";

export const refreshSessionArgs = v.object({
refreshToken: v.string(),
Expand All @@ -26,6 +30,7 @@ export async function refreshSessionImpl(
args: Infer<typeof refreshSessionArgs>,
getProviderOrThrow: Provider.GetProviderOrThrowFunc,
config: Provider.Config,
runtimeEnv: AuthRuntimeEnv,
): Promise<ReturnType> {
const { refreshToken } = args;
const { refreshTokenId, sessionId: tokenSessionId } =
Expand Down Expand Up @@ -63,7 +68,7 @@ export async function refreshSessionImpl(
await ctx.db.patch(refreshTokenId, {
firstUsedTime: Date.now(),
});
const result = await generateTokensForSession(ctx, config, {
const result = await generateTokensForSession(ctx, config, runtimeEnv, {
userId,
sessionId,
issuedRefreshTokenId: null,
Expand Down Expand Up @@ -95,7 +100,7 @@ export async function refreshSessionImpl(
`Token ${maybeRedact(validationResult.refreshTokenDoc._id)} is parent of active refresh token ${maybeRedact(activeRefreshToken._id)}, so returning that token`,
);

const result = await generateTokensForSession(ctx, config, {
const result = await generateTokensForSession(ctx, config, runtimeEnv, {
userId,
sessionId,
issuedRefreshTokenId: activeRefreshToken._id,
Expand All @@ -106,7 +111,7 @@ export async function refreshSessionImpl(

// Check if within reuse window
if (tokenFirstUsed + REFRESH_TOKEN_REUSE_WINDOW_MS > Date.now()) {
const result = await generateTokensForSession(ctx, config, {
const result = await generateTokensForSession(ctx, config, runtimeEnv, {
userId,
sessionId,
issuedRefreshTokenId: null,
Expand Down Expand Up @@ -150,5 +155,6 @@ export const callRefreshSession = async (
type: "refreshSession",
...args,
},
env: collectRuntimeEnv(),
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "../rateLimit.js";
import * as Provider from "../provider.js";
import { LOG_LEVELS, logWithLevel, maybeRedact } from "../utils.js";
import { collectRuntimeEnv } from "../runtimeEnv.js";

export const retrieveAccountWithCredentialsArgs = v.object({
provider: v.string(),
Expand Down Expand Up @@ -74,5 +75,6 @@ export const callRetreiveAccountWithCredentials = async (
type: "retrieveAccountWithCredentials",
...args,
},
env: collectRuntimeEnv(),
});
};
7 changes: 7 additions & 0 deletions src/server/implementation/mutations/signIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
maybeGenerateTokensForSession,
} from "../sessions.js";
import { LOG_LEVELS, logWithLevel } from "../utils.js";
import {
AuthRuntimeEnv,
collectRuntimeEnv,
} from "../runtimeEnv.js";

export const signInArgs = v.object({
userId: v.id("users"),
Expand All @@ -19,6 +23,7 @@ export async function signInImpl(
ctx: MutationCtx,
args: Infer<typeof signInArgs>,
config: Provider.Config,
runtimeEnv: AuthRuntimeEnv,
): Promise<ReturnType> {
logWithLevel(LOG_LEVELS.DEBUG, "signInImpl args:", args);
const { userId, sessionId: existingSessionId, generateTokens } = args;
Expand All @@ -28,6 +33,7 @@ export async function signInImpl(
return await maybeGenerateTokensForSession(
ctx,
config,
runtimeEnv,
userId,
sessionId,
generateTokens,
Expand All @@ -43,5 +49,6 @@ export const callSignIn = async (
type: "signIn",
...args,
},
env: collectRuntimeEnv(),
});
};
2 changes: 2 additions & 0 deletions src/server/implementation/mutations/signOut.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GenericId } from "convex/values";
import { ActionCtx, MutationCtx } from "../types.js";
import { deleteSession, getAuthSessionId } from "../sessions.js";
import { collectRuntimeEnv } from "../runtimeEnv.js";

type ReturnType = {
userId: GenericId<"users">;
Expand All @@ -24,5 +25,6 @@ export const callSignOut = async (ctx: ActionCtx): Promise<void> => {
args: {
type: "signOut",
},
env: collectRuntimeEnv(),
});
};
2 changes: 2 additions & 0 deletions src/server/implementation/mutations/userOAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Provider from "../provider.js";
import { OAuthConfig } from "@auth/core/providers/oauth.js";
import { upsertUserAndAccount } from "../users.js";
import { generateRandomString, logWithLevel, sha256 } from "../utils.js";
import { collectRuntimeEnv } from "../runtimeEnv.js";

const OAUTH_SIGN_IN_EXPIRATION_MS = 1000 * 60 * 2; // 2 minutes

Expand Down Expand Up @@ -78,5 +79,6 @@ export const callUserOAuth = async (
type: "userOAuth",
...args,
},
env: collectRuntimeEnv(),
});
};
2 changes: 2 additions & 0 deletions src/server/implementation/mutations/verifier.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GenericId } from "convex/values";
import { ActionCtx, MutationCtx } from "../types.js";
import { getAuthSessionId } from "../sessions.js";
import { collectRuntimeEnv } from "../runtimeEnv.js";

type ReturnType = GenericId<"authVerifiers">;

Expand All @@ -15,5 +16,6 @@ export const callVerifier = async (ctx: ActionCtx): Promise<ReturnType> => {
args: {
type: "verifier",
},
env: collectRuntimeEnv(),
});
};
Loading