init
This commit is contained in:
38
src/lib/supertokens/client.ts
Normal file
38
src/lib/supertokens/client.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import SuperTokens from 'supertokens-web-js';
|
||||
import Session from 'supertokens-web-js/recipe/session';
|
||||
import Passwordless from 'supertokens-web-js/recipe/passwordless';
|
||||
import { appInfo } from './config';
|
||||
import { logger } from './';
|
||||
|
||||
export const frontendConfig = () => {
|
||||
return {
|
||||
appInfo,
|
||||
recipeList: [
|
||||
Passwordless.init(),
|
||||
Session.init({
|
||||
tokenTransferMethod: "cookie",
|
||||
sessionTokenBackendDomain: undefined,
|
||||
|
||||
preAPIHook: async (context) => {
|
||||
context.requestInit.credentials = "include";
|
||||
return context;
|
||||
},
|
||||
})
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
let initialized = false;
|
||||
export function ensureSuperTokensFrontend() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
if (!initialized) {
|
||||
SuperTokens.init(frontendConfig());
|
||||
initialized = true;
|
||||
logger.info("Initialized");
|
||||
|
||||
Session.doesSessionExist().then(exists => {
|
||||
logger.info(`Session does${exists ? '' : 'NOT'} exist on load!`);
|
||||
});
|
||||
}
|
||||
}
|
||||
7
src/lib/supertokens/config.ts
Normal file
7
src/lib/supertokens/config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const appInfo = {
|
||||
appName: 'Tanstack Start SuperTokens',
|
||||
apiDomain: import.meta.env.VITE_API_DOMAIN || 'http://localhost:3000',
|
||||
websiteDomain: import.meta.env.VITE_WEBSITE_DOMAIN || 'http://localhost:3000',
|
||||
apiBasePath: '/api/auth',
|
||||
websiteBasePath: '/auth',
|
||||
}
|
||||
4
src/lib/supertokens/index.ts
Normal file
4
src/lib/supertokens/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Logger } from "@/lib/logger";
|
||||
const logger = new Logger('SuperTokens');
|
||||
|
||||
export { logger };
|
||||
30
src/lib/supertokens/recipes/passwordless-development-mode.ts
Normal file
30
src/lib/supertokens/recipes/passwordless-development-mode.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import Passwordless from "supertokens-node/recipe/passwordless";
|
||||
import { logger } from "../";
|
||||
|
||||
const init = () => (
|
||||
Passwordless.init({
|
||||
flowType: "USER_INPUT_CODE",
|
||||
contactMethod: "PHONE",
|
||||
smsDelivery: {
|
||||
override: (originalImplementation) => {
|
||||
return {
|
||||
...originalImplementation,
|
||||
sendSms: async ({ userInputCode }) => {
|
||||
if (!userInputCode) {
|
||||
throw new Error("No user input code provided to sendSms");
|
||||
}
|
||||
|
||||
logger.info('Sending Code',
|
||||
'######################',
|
||||
'## SuperTokens Code ##',
|
||||
`## ${userInputCode} ##`,
|
||||
'######################'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
export default { init };
|
||||
65
src/lib/supertokens/recipes/passwordless-twilio-verify.ts
Normal file
65
src/lib/supertokens/recipes/passwordless-twilio-verify.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useSession } from "@tanstack/react-start/server";
|
||||
import Passwordless from "supertokens-node/recipe/passwordless";
|
||||
import { sendVerifyCode, updateVerify } from "@/lib/twilio";
|
||||
|
||||
const init = () => (
|
||||
Passwordless.init({
|
||||
flowType: "USER_INPUT_CODE",
|
||||
contactMethod: "PHONE",
|
||||
smsDelivery: {
|
||||
override: (originalImplementation) => {
|
||||
return {
|
||||
...originalImplementation,
|
||||
sendSms: async ({ userInputCode, phoneNumber, preAuthSessionId }) => {
|
||||
if (!userInputCode) {
|
||||
throw new Error("No user input code provided to sendSms");
|
||||
}
|
||||
|
||||
const sid = await sendVerifyCode(phoneNumber, userInputCode);
|
||||
|
||||
const session = await useSession({
|
||||
password: preAuthSessionId
|
||||
});
|
||||
|
||||
await session.update({
|
||||
twilioSid: sid
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
override: {
|
||||
functions: (originalImplementation) => {
|
||||
return {
|
||||
...originalImplementation,
|
||||
consumeCode: async (input) => {
|
||||
const session = await useSession({ password: input.preAuthSessionId });
|
||||
const twilioSid = session?.data.twilioSid;
|
||||
|
||||
if (!twilioSid) {
|
||||
throw new Error("Twilio SID not found in session");
|
||||
}
|
||||
|
||||
let response = await originalImplementation.consumeCode(input);
|
||||
|
||||
if (response.status === "OK") {
|
||||
await updateVerify(twilioSid);
|
||||
await session.update({
|
||||
twilioSid: undefined,
|
||||
userId: response?.user.id
|
||||
})
|
||||
} else if (response.status === "INCORRECT_USER_INPUT_CODE_ERROR") {
|
||||
if (response.failedCodeInputAttemptCount !== response.maximumCodeInputAttempts) {
|
||||
await updateVerify(twilioSid);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
export default { init };
|
||||
48
src/lib/supertokens/recipes/start-session.ts
Normal file
48
src/lib/supertokens/recipes/start-session.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { getSessionForSSR } from "supertokens-node/custom";
|
||||
import { ensureSuperTokensBackend } from "../server";
|
||||
import { logger } from "../";
|
||||
|
||||
export async function getSessionForStart(request: Request, options?: { sessionRequired?: boolean }) {
|
||||
ensureSuperTokensBackend();
|
||||
|
||||
try {
|
||||
const session = await getSessionForSSR(request);
|
||||
|
||||
if (session.hasToken) {
|
||||
return {
|
||||
hasToken: true,
|
||||
accessTokenPayload: session.accessTokenPayload,
|
||||
userId: session.accessTokenPayload?.sub,
|
||||
sessionHandle: session.accessTokenPayload?.sessionHandle,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error: any) {
|
||||
logger.error("Session error", error);
|
||||
|
||||
if (error.type === "TRY_REFRESH_TOKEN") {
|
||||
return {
|
||||
hasToken: false,
|
||||
needsRefresh: true,
|
||||
error: 'TRY_REFRESH_TOKEN'
|
||||
};
|
||||
}
|
||||
|
||||
if (options?.sessionRequired === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifySession(request: Request, options?: { sessionRequired?: boolean }) {
|
||||
const session = await getSessionForStart(request, options);
|
||||
|
||||
if (!session && options?.sessionRequired !== false) {
|
||||
throw new Response("Unauthorized", { status: 401 });
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
42
src/lib/supertokens/server.ts
Normal file
42
src/lib/supertokens/server.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import SuperTokens from "supertokens-node";
|
||||
import Session from "supertokens-node/recipe/session";
|
||||
import { TypeInput } from "supertokens-node/types";
|
||||
import Dashboard from "supertokens-node/recipe/dashboard";
|
||||
import UserRoles from "supertokens-node/recipe/userroles";
|
||||
import { appInfo } from "./config";
|
||||
import PasswordlessDevelopmentMode from "./recipes/passwordless-development-mode";
|
||||
import { logger } from "./";
|
||||
|
||||
export const backendConfig = (): TypeInput => {
|
||||
return {
|
||||
framework: 'custom',
|
||||
supertokens: {
|
||||
connectionURI: import.meta.env.VITE_SUPERTOKENS_URI || "https://try.supertokens.io",
|
||||
},
|
||||
appInfo,
|
||||
recipeList: [
|
||||
PasswordlessDevelopmentMode.init(),
|
||||
Session.init({
|
||||
cookieSameSite: "lax",
|
||||
cookieSecure: process.env.NODE_ENV === 'production',
|
||||
cookieDomain: process.env.NODE_ENV === 'production' ? ".example.com" : undefined,
|
||||
antiCsrf: process.env.NODE_ENV === 'production' ? "VIA_TOKEN" : "NONE",
|
||||
|
||||
// Debug only
|
||||
exposeAccessTokenToFrontendInCookieBasedAuth: true,
|
||||
}),
|
||||
Dashboard.init(),
|
||||
UserRoles.init()
|
||||
],
|
||||
telemetry: process.env.NODE_ENV !== 'production',
|
||||
};
|
||||
}
|
||||
|
||||
let initialized = false;
|
||||
export function ensureSuperTokensBackend() {
|
||||
if (!initialized) {
|
||||
SuperTokens.init(backendConfig());
|
||||
initialized = true;
|
||||
logger.simple("Backend initialized");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user