diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts deleted file mode 100644 index d066bd544..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -import axios, { type AxiosError } from 'axios' - -import config from 'wasp/core/config' -import { storage } from 'wasp/core/storage' -import { apiEventsEmitter } from './events.js' - -// PUBLIC API -export const api = axios.create({ - baseURL: config.apiUrl, -}) - -const WASP_APP_AUTH_SESSION_ID_NAME = 'sessionId' - -let waspAppAuthSessionId = storage.get(WASP_APP_AUTH_SESSION_ID_NAME) as string | undefined - -// PRIVATE API (sdk) -export function setSessionId(sessionId: string): void { - waspAppAuthSessionId = sessionId - storage.set(WASP_APP_AUTH_SESSION_ID_NAME, sessionId) - apiEventsEmitter.emit('sessionId.set') -} - -// PRIVATE API (sdk) -export function getSessionId(): string | undefined { - return waspAppAuthSessionId -} - -// PRIVATE API (sdk) -export function clearSessionId(): void { - waspAppAuthSessionId = undefined - storage.remove(WASP_APP_AUTH_SESSION_ID_NAME) - apiEventsEmitter.emit('sessionId.clear') -} - -// PRIVATE API (sdk) -export function removeLocalUserData(): void { - waspAppAuthSessionId = undefined - storage.clear() - apiEventsEmitter.emit('sessionId.clear') -} - -api.interceptors.request.use((request) => { - const sessionId = getSessionId() - if (sessionId) { - request.headers['Authorization'] = `Bearer ${sessionId}` - } - return request -}) - -api.interceptors.response.use(undefined, (error) => { - if (error.response?.status === 401) { - clearSessionId() - } - return Promise.reject(error) -}) - -// This handler will run on other tabs (not the active one calling API functions), -// and will ensure they know about auth session ID changes. -// Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event -// "Note: This won't work on the same page that is making the changes — it is really a way -// for other pages on the domain using the storage to sync any changes that are made." -window.addEventListener('storage', (event) => { - if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) { - if (!!event.newValue) { - waspAppAuthSessionId = event.newValue - apiEventsEmitter.emit('sessionId.set') - } else { - waspAppAuthSessionId = undefined - apiEventsEmitter.emit('sessionId.clear') - } - } -}) - -// PRIVATE API (sdk) -/** - * Takes an error returned by the app's API (as returned by axios), and transforms into a more - * standard format to be further used by the client. It is also assumed that given API - * error has been formatted as implemented by HttpError on the server. - */ -export function handleApiError(error: AxiosError<{ message?: string, data?: unknown }>): void { - if (error?.response) { - // If error came from HTTP response, we capture most informative message - // and also add .statusCode information to it. - // If error had JSON response, we assume it is of format { message, data } and - // add that info to the error. - // TODO: We might want to use HttpError here instead of just Error, since - // HttpError is also used on server to throw errors like these. - // That would require copying HttpError code to web-app also and using it here. - const responseJson = error.response?.data - const responseStatusCode = error.response.status - throw new WaspHttpError(responseStatusCode, responseJson?.message ?? error.message, responseJson) - } else { - // If any other error, we just propagate it. - throw error - } -} - -class WaspHttpError extends Error { - statusCode: number - - data: unknown - - constructor (statusCode: number, message: string, data: unknown) { - super(message) - this.statusCode = statusCode - this.data = data - } -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx deleted file mode 100644 index c612e3386..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { useState, createContext } from 'react' -import { createTheme } from '@stitches/react' -import { styled } from 'wasp/core/stitches.config' - -import { - type State, - type CustomizationOptions, - type ErrorMessage, - type AdditionalSignupFields, -} from './types' -import { LoginSignupForm } from './internal/common/LoginSignupForm' -import { MessageError, MessageSuccess } from './internal/Message' -import { ForgotPasswordForm } from './internal/email/ForgotPasswordForm' -import { ResetPasswordForm } from './internal/email/ResetPasswordForm' -import { VerifyEmailForm } from './internal/email/VerifyEmailForm' - -const logoStyle = { - height: '3rem' -} - -const Container = styled('div', { - display: 'flex', - flexDirection: 'column', -}) - -const HeaderText = styled('h2', { - fontSize: '1.875rem', - fontWeight: '700', - marginTop: '1.5rem' -}) - - -// PRIVATE API -export const AuthContext = createContext({ - isLoading: false, - setIsLoading: (isLoading: boolean) => {}, - setErrorMessage: (errorMessage: ErrorMessage | null) => {}, - setSuccessMessage: (successMessage: string | null) => {}, -}) - -function Auth ({ state, appearance, logo, socialLayout = 'horizontal', additionalSignupFields }: { - state: State; -} & CustomizationOptions & { - additionalSignupFields?: AdditionalSignupFields; -}) { - const [errorMessage, setErrorMessage] = useState(null); - const [successMessage, setSuccessMessage] = useState(null); - const [isLoading, setIsLoading] = useState(false); - - // TODO(matija): this is called on every render, is it a problem? - // If we do it in useEffect(), then there is a glitch between the default color and the - // user provided one. - const customTheme = createTheme(appearance ?? {}) - - const titles: Record = { - login: 'Log in to your account', - signup: 'Create a new account', - "forgot-password": "Forgot your password?", - "reset-password": "Reset your password", - "verify-email": "Email verification", - } - const title = titles[state] - - const socialButtonsDirection = socialLayout === 'vertical' ? 'vertical' : 'horizontal' - - return ( - -
- {logo && (Your Company)} - {title} -
- - {errorMessage && ( - - {errorMessage.title}{errorMessage.description && ': '}{errorMessage.description} - - )} - {successMessage && {successMessage}} - - {(state === 'login' || state === 'signup') && ( - - )} - {state === 'forgot-password' && ()} - {state === 'reset-password' && ()} - {state === 'verify-email' && ()} - -
- ) -} - -// PRIVATE API -export default Auth; diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx deleted file mode 100644 index f8fca6608..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import Auth from './Auth' -import { type CustomizationOptions, State } from './types' - -// PUBLIC API -export function LoginForm({ - appearance, - logo, - socialLayout, -}: CustomizationOptions) { - return ( - - ) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx deleted file mode 100644 index 32c7afc38..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import Auth from './Auth' -import { - type CustomizationOptions, - type AdditionalSignupFields, - State, -} from './types' - -// PUBLIC API -export function SignupForm({ - appearance, - logo, - socialLayout, - additionalFields, -}: CustomizationOptions & { additionalFields?: AdditionalSignupFields; }) { - return ( - - ) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx deleted file mode 100644 index 163430742..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { styled } from 'wasp/core/stitches.config' - -// PRIVATE API -export const Form = styled('form', { - marginTop: '1.5rem', -}) - -// PUBLIC API -export const FormItemGroup = styled('div', { - '& + div': { - marginTop: '1.5rem', - }, -}) - -// PUBLIC API -export const FormLabel = styled('label', { - display: 'block', - fontSize: '$sm', - fontWeight: '500', - marginBottom: '0.5rem', -}) - -const commonInputStyles = { - display: 'block', - lineHeight: '1.5rem', - fontSize: '$sm', - borderWidth: '1px', - borderColor: '$gray600', - backgroundColor: '#f8f4ff', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - '&:focus': { - borderWidth: '1px', - borderColor: '$gray700', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - }, - '&:disabled': { - opacity: 0.5, - cursor: 'not-allowed', - backgroundColor: '$gray400', - borderColor: '$gray400', - color: '$gray500', - }, - - borderRadius: '0.375rem', - width: '100%', - - paddingTop: '0.375rem', - paddingBottom: '0.375rem', - paddingLeft: '0.75rem', - paddingRight: '0.75rem', - margin: 0, -} - -// PUBLIC API -export const FormInput = styled('input', commonInputStyles) - -// PUBLIC API -export const FormTextarea = styled('textarea', commonInputStyles) - -// PUBLIC API -export const FormError = styled('div', { - display: 'block', - fontSize: '$sm', - fontWeight: '500', - color: '$formErrorText', - marginTop: '0.5rem', -}) - -// PRIVATE API -export const SubmitButton = styled('button', { - display: 'flex', - justifyContent: 'center', - - width: '100%', - borderWidth: '1px', - borderColor: '$brand', - backgroundColor: '$brand', - color: '$submitButtonText', - - padding: '0.5rem 0.75rem', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - - fontWeight: '600', - fontSize: '$sm', - lineHeight: '1.25rem', - borderRadius: '0.375rem', - - // TODO(matija): extract this into separate BaseButton component and then inherit it. - '&:hover': { - backgroundColor: '$brandAccent', - borderColor: '$brandAccent', - }, - '&:disabled': { - opacity: 0.5, - cursor: 'not-allowed', - backgroundColor: '$gray400', - borderColor: '$gray400', - color: '$gray500', - }, - transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', - transitionDuration: '100ms', -}) diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx deleted file mode 100644 index 362ff9dfd..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { styled } from 'wasp/core/stitches.config' - -// PRIVATE API -export const Message = styled('div', { - padding: '0.5rem 0.75rem', - borderRadius: '0.375rem', - marginTop: '1rem', - background: '$gray400', -}) - -// PRIVATE API -export const MessageError = styled(Message, { - background: '$errorBackground', - color: '$errorText', -}) - -// PRIVATE API -export const MessageSuccess = styled(Message, { - background: '$successBackground', - color: '$successText', -}) diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx deleted file mode 100644 index db7519fce..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { useContext } from 'react' -import { useForm, UseFormReturn } from 'react-hook-form' -import { styled } from 'wasp/core/stitches.config' -import config from 'wasp/core/config' - -import { AuthContext } from '../../Auth' -import { - Form, - FormInput, - FormItemGroup, - FormLabel, - FormError, - FormTextarea, - SubmitButton, -} from '../Form' -import type { - AdditionalSignupFields, - AdditionalSignupField, - AdditionalSignupFieldRenderFn, - FormState, -} from '../../types' -import { useHistory } from 'react-router-dom' -import { useEmail } from '../email/useEmail' - - -// PRIVATE API -export type LoginSignupFormFields = { - [key: string]: string; -} - -// PRIVATE API -export const LoginSignupForm = ({ - state, - socialButtonsDirection = 'horizontal', - additionalSignupFields, -}: { - state: 'login' | 'signup' - socialButtonsDirection?: 'horizontal' | 'vertical' - additionalSignupFields?: AdditionalSignupFields -}) => { - const { - isLoading, - setErrorMessage, - setSuccessMessage, - setIsLoading, - } = useContext(AuthContext) - const isLogin = state === 'login' - const cta = isLogin ? 'Log in' : 'Sign up'; - const history = useHistory(); - const onErrorHandler = (error) => { - setErrorMessage({ title: error.message, description: error.data?.data?.message }) - }; - const hookForm = useForm() - const { register, formState: { errors }, handleSubmit: hookFormHandleSubmit } = hookForm - const { handleSubmit } = useEmail({ - isLogin, - onError: onErrorHandler, - showEmailVerificationPending() { - hookForm.reset() - setSuccessMessage(`You've signed up successfully! Check your email for the confirmation link.`) - }, - onLoginSuccess() { - history.push('/') - }, - }); - async function onSubmit (data) { - setIsLoading(true); - setErrorMessage(null); - setSuccessMessage(null); - try { - await handleSubmit(data); - } finally { - setIsLoading(false); - } - } - - return (<> -
- - E-mail - - {errors.email && {errors.email.message}} - - - Password - - {errors.password && {errors.password.message}} - - - - {cta} - - - ) -} - -function AdditionalFormFields({ - hookForm, - formState: { isLoading }, - additionalSignupFields, -}: { - hookForm: UseFormReturn; - formState: FormState; - additionalSignupFields: AdditionalSignupFields; -}) { - const { - register, - formState: { errors }, - } = hookForm; - - function renderField>( - field: AdditionalSignupField, - // Ideally we would use ComponentType here, but it doesn't work with react-hook-form - Component: any, - props?: React.ComponentProps - ) { - return ( - - {field.label} - - {errors[field.name] && ( - {errors[field.name].message} - )} - - ); - } - - if (areAdditionalFieldsRenderFn(additionalSignupFields)) { - return additionalSignupFields(hookForm, { isLoading }) - } - - return ( - additionalSignupFields && - additionalSignupFields.map((field) => { - if (isFieldRenderFn(field)) { - return field(hookForm, { isLoading }) - } - switch (field.type) { - case 'input': - return renderField(field, FormInput, { - type: 'text', - }) - case 'textarea': - return renderField(field, FormTextarea) - default: - throw new Error( - `Unsupported additional signup field type: ${field.type}` - ) - } - }) - ) -} - -function isFieldRenderFn( - additionalSignupField: AdditionalSignupField | AdditionalSignupFieldRenderFn -): additionalSignupField is AdditionalSignupFieldRenderFn { - return typeof additionalSignupField === 'function' -} - -function areAdditionalFieldsRenderFn( - additionalSignupFields: AdditionalSignupFields -): additionalSignupFields is AdditionalSignupFieldRenderFn { - return typeof additionalSignupFields === 'function' -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts deleted file mode 100644 index 515647268..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { createTheme } from '@stitches/react' -import { UseFormReturn, RegisterOptions } from 'react-hook-form' -import type { LoginSignupFormFields } from './internal/common/LoginSignupForm' - -// PRIVATE API -export enum State { - Login = 'login', - Signup = 'signup', - ForgotPassword = 'forgot-password', - ResetPassword = 'reset-password', - VerifyEmail = 'verify-email', -} - -// PUBLIC API -export type CustomizationOptions = { - logo?: string - socialLayout?: 'horizontal' | 'vertical' - appearance?: Parameters[0] -} - -// PRIVATE API -export type ErrorMessage = { - title: string - description?: string -} - -// PRIVATE API -export type FormState = { - isLoading: boolean -} - -// PRIVATE API -export type AdditionalSignupFieldRenderFn = ( - hookForm: UseFormReturn, - formState: FormState -) => React.ReactNode - -// PRIVATE API -export type AdditionalSignupField = { - name: string - label: string - type: 'input' | 'textarea' - validations?: RegisterOptions -} - -// PRIVATE API -export type AdditionalSignupFields = - | (AdditionalSignupField | AdditionalSignupFieldRenderFn)[] - | AdditionalSignupFieldRenderFn diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts deleted file mode 100644 index 050dd3f32..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { setSessionId } from 'wasp/client/api' -import { invalidateAndRemoveQueries } from 'wasp/operations/resources' - -// PRIVATE API -export async function initSession(sessionId: string): Promise { - setSessionId(sessionId) - // We need to invalidate queries after login in order to get the correct user - // data in the React components (using `useAuth`). - // Redirects after login won't work properly without this. - - // TODO(filip): We are currently removing all the queries, but we should - // remove only non-public, user-dependent queries - public queries are - // expected not to change in respect to the currently logged in user. - await invalidateAndRemoveQueries() -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts deleted file mode 100644 index 4a5181756..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { api, removeLocalUserData } from 'wasp/client/api' -import { invalidateAndRemoveQueries } from 'wasp/operations/resources' - -// PUBLIC API -export default async function logout(): Promise { - try { - await api.post('/auth/logout') - } finally { - // Even if the logout request fails, we still want to remove the local user data - // in case the logout failed because of a network error and the user walked away - // from the computer. - removeLocalUserData() - - // TODO(filip): We are currently invalidating and removing all the queries, but - // we should remove only the non-public, user-dependent ones. - await invalidateAndRemoveQueries() - } -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts deleted file mode 100644 index 8cd06b8af..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Router, Request } from 'express' -import type { Prisma } from '@prisma/client' -import type { Expand } from 'wasp/universal/types' -import type { ProviderName } from '../utils' - -// PUBLIC API -export function defineUserSignupFields(fields: UserSignupFields) { - return fields -} - -type UserEntityCreateInput = Prisma.UserCreateInput - -// PRIVATE API -export type ProviderConfig = { - // Unique provider identifier, used as part of URL paths - id: ProviderName; - displayName: string; - // Each provider config can have an init method which is ran on setup time - // e.g. for oAuth providers this is the time when the Passport strategy is registered. - init?(provider: ProviderConfig): Promise; - // Every provider must have a setupRouter method which returns the Express router. - // In this function we are flexibile to do what ever is necessary to make the provider work. - createRouter(provider: ProviderConfig, initData: InitData): Router; -}; - -// PRIVATE API -export type InitData = { - [key: string]: any; -} - -// PRIVATE API -export type RequestWithWasp = Request & { wasp?: { [key: string]: any } } - -// PRIVATE API -export type PossibleUserFields = Expand> - -// PRIVATE API -export type UserSignupFields = { - [key in keyof PossibleUserFields]: FieldGetter< - PossibleUserFields[key] - > -} - -type FieldGetter = ( - data: { [key: string]: unknown } -) => Promise | T | undefined diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts deleted file mode 100644 index 03d33b501..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts +++ /dev/null @@ -1,2 +0,0 @@ -// todo(filip): turn into a proper import/path -export type { AuthUser, ProviderName, DeserializedAuthIdentity } from 'wasp/server/_types' diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts deleted file mode 100644 index cd7630ade..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { deserialize as superjsonDeserialize } from 'superjson' -import { useQuery } from 'wasp/rpc' -import { api, handleApiError } from 'wasp/client/api' -import { HttpMethod } from 'wasp/types' -import type { AuthUser } from './types' -import { addMetadataToQuery } from 'wasp/rpc/queries' - -// PUBLIC API -export const getMe = createUserGetter() - -// PUBLIC API -export default function useAuth(queryFnArgs?: unknown, config?: any) { - return useQuery(getMe, queryFnArgs, config) -} - -function createUserGetter() { - const getMeRelativePath = 'auth/me' - const getMeRoute = { method: HttpMethod.Get, path: `/${getMeRelativePath}` } - async function getMe(): Promise { - try { - const response = await api.get(getMeRoute.path) - - return superjsonDeserialize(response.data) - } catch (error) { - if (error.response?.status === 401) { - return null - } else { - handleApiError(error) - } - } - } - - addMetadataToQuery(getMe, { - relativeQueryPath: getMeRelativePath, - queryRoute: getMeRoute, - entitiesUsed: ['User'], - }) - - return getMe -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts deleted file mode 100644 index f9bc6d39a..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { AuthUser, ProviderName, DeserializedAuthIdentity } from './types' - -// PUBLIC API -export function getEmail(user: AuthUser): string | null { - return findUserIdentity(user, "email")?.providerUserId ?? null; -} - -// PUBLIC API -export function getUsername(user: AuthUser): string | null { - return findUserIdentity(user, "username")?.providerUserId ?? null; -} - -// PUBLIC API -export function getFirstProviderUserId(user?: AuthUser): string | null { - if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) { - return null; - } - - return user.auth.identities[0].providerUserId ?? null; -} - -// PUBLIC API -export function findUserIdentity(user: AuthUser, providerName: ProviderName): DeserializedAuthIdentity | undefined { - return user.auth.identities.find( - (identity) => identity.providerName === providerName - ); -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts deleted file mode 100644 index ba04768d7..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts +++ /dev/null @@ -1,321 +0,0 @@ -import { hashPassword } from './password.js' -import { verify } from './jwt.js' -import { prisma, HttpError, AuthError } from 'wasp/server' -import { sleep } from 'wasp/server/utils' -import { - type User, - type Auth, - type AuthIdentity, -} from 'wasp/entities' -import { Prisma } from '@prisma/client'; - -import { throwValidationError } from './validation.js' - -import { type UserSignupFields, type PossibleUserFields } from './providers/types.js' - -// PRIVATE API -export type EmailProviderData = { - hashedPassword: string; - isEmailVerified: boolean; - emailVerificationSentAt: string | null; - passwordResetSentAt: string | null; -} - -// PRIVATE API -export type UsernameProviderData = { - hashedPassword: string; -} - -// PRIVATE API -export type OAuthProviderData = {} - -// PRIVATE API -/** - * This type is used for type-level programming e.g. to enumerate - * all possible provider data types. - * - * The keys of this type are the names of the providers and the values - * are the types of the provider data. - */ -export type PossibleProviderData = { - email: EmailProviderData; - username: UsernameProviderData; - google: OAuthProviderData; - github: OAuthProviderData; -} - -// PRIVATE API -export type ProviderName = keyof PossibleProviderData - -// PRIVATE API -export const contextWithUserEntity = { - entities: { - User: prisma.user - } -} - -// PRIVATE API -export const authConfig = { - failureRedirectPath: "/login", - successRedirectPath: "/", -} - -// PRIVATE API -/** - * ProviderId uniquely identifies an auth identity e.g. - * "email" provider with user id "test@test.com" or - * "google" provider with user id "1234567890". - * - * We use this type to avoid passing the providerName and providerUserId - * separately. Also, we can normalize the providerUserId to make sure it's - * consistent across different DB operations. - */ -export type ProviderId = { - providerName: ProviderName; - providerUserId: string; -} - -// PUBLIC API -export function createProviderId(providerName: ProviderName, providerUserId: string): ProviderId { - return { - providerName, - providerUserId: providerUserId.toLowerCase(), - } -} - -// PUBLIC API -export async function findAuthIdentity(providerId: ProviderId): Promise { - return prisma.authIdentity.findUnique({ - where: { - providerName_providerUserId: providerId, - } - }); -} - -// PUBLIC API -/** - * Updates the provider data for the given auth identity. - * - * This function performs data sanitization and serialization. - * Sanitization is done by hashing the password, so this function - * expects the password received in the `providerDataUpdates` - * **not to be hashed**. - */ -export async function updateAuthIdentityProviderData( - providerId: ProviderId, - existingProviderData: PossibleProviderData[PN], - providerDataUpdates: Partial, -): Promise { - // We are doing the sanitization here only on updates to avoid - // hashing the password multiple times. - const sanitizedProviderDataUpdates = await sanitizeProviderData(providerDataUpdates); - const newProviderData = { - ...existingProviderData, - ...sanitizedProviderDataUpdates, - } - const serializedProviderData = await serializeProviderData(newProviderData); - return prisma.authIdentity.update({ - where: { - providerName_providerUserId: providerId, - }, - data: { providerData: serializedProviderData }, - }); -} - -type FindAuthWithUserResult = Auth & { - user: User -} - -// PRIVATE API -export async function findAuthWithUserBy( - where: Prisma.AuthWhereInput -): Promise { - return prisma.auth.findFirst({ where, include: { user: true }}); -} - -// PUBLIC API -export async function createUser( - providerId: ProviderId, - serializedProviderData?: string, - userFields?: PossibleUserFields, -): Promise { - return prisma.user.create({ - data: { - // Using any here to prevent type errors when userFields are not - // defined. We want Prisma to throw an error in that case. - ...(userFields ?? {} as any), - auth: { - create: { - identities: { - create: { - providerName: providerId.providerName, - providerUserId: providerId.providerUserId, - providerData: serializedProviderData, - }, - }, - } - }, - }, - // We need to include the Auth entity here because we need `authId` - // to be able to create a session. - include: { - auth: true, - }, - }) -} - -// PRIVATE API -export async function deleteUserByAuthId(authId: string): Promise<{ count: number }> { - return prisma.user.deleteMany({ where: { auth: { - id: authId, - } } }) -} - -// PRIVATE API -export async function verifyToken(token: string): Promise { - return verify(token); -} - -// PRIVATE API -// If an user exists, we don't want to leak information -// about it. Pretending that we're doing some work -// will make it harder for an attacker to determine -// if a user exists or not. -// NOTE: Attacker measuring time to response can still determine -// if a user exists or not. We'll be able to avoid it when -// we implement e-mail sending via jobs. -export async function doFakeWork(): Promise { - const timeToWork = Math.floor(Math.random() * 1000) + 1000; - return sleep(timeToWork); -} - -// PRIVATE API -export function rethrowPossibleAuthError(e: unknown): void { - if (e instanceof AuthError) { - throwValidationError(e.message); - } - - // Prisma code P2002 is for unique constraint violations. - if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') { - throw new HttpError(422, 'Save failed', { - message: `user with the same identity already exists`, - }) - } - - if (e instanceof Prisma.PrismaClientValidationError) { - // NOTE: Logging the error since this usually means that there are - // required fields missing in the request, we want the developer - // to know about it. - console.error(e) - throw new HttpError(422, 'Save failed', { - message: 'there was a database error' - }) - } - - // Prisma code P2021 is for missing table errors. - if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2021') { - // NOTE: Logging the error since this usually means that the database - // migrations weren't run, we want the developer to know about it. - console.error(e) - console.info('🐝 This error can happen if you did\'t run the database migrations.') - throw new HttpError(500, 'Save failed', { - message: `there was a database error`, - }) - } - - // Prisma code P2003 is for foreign key constraint failure - if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2003') { - console.error(e) - console.info(`🐝 This error can happen if you have some relation on your User entity - but you didn't specify the "onDelete" behaviour to either "Cascade" or "SetNull". - Read more at: https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/referential-actions`) - throw new HttpError(500, 'Save failed', { - message: `there was a database error`, - }) - } - - throw e -} - -// PRIVATE API -export async function validateAndGetUserFields( - data: { - [key: string]: unknown - }, - userSignupFields?: UserSignupFields, -): Promise> { - const { - password: _password, - ...sanitizedData - } = data; - const result: Record = {}; - - if (!userSignupFields) { - return result; - } - - for (const [field, getFieldValue] of Object.entries(userSignupFields)) { - try { - const value = await getFieldValue(sanitizedData) - result[field] = value - } catch (e) { - throwValidationError(e.message) - } - } - return result; -} - -// PUBLIC API -export function deserializeAndSanitizeProviderData( - providerData: string, - { shouldRemovePasswordField = false }: { shouldRemovePasswordField?: boolean } = {}, -): PossibleProviderData[PN] { - // NOTE: We are letting JSON.parse throw an error if the providerData is not valid JSON. - let data = JSON.parse(providerData) as PossibleProviderData[PN]; - - if (providerDataHasPasswordField(data) && shouldRemovePasswordField) { - delete data.hashedPassword; - } - - return data; -} - -// PUBLIC API -export async function sanitizeAndSerializeProviderData( - providerData: PossibleProviderData[PN], -): Promise { - return serializeProviderData( - await sanitizeProviderData(providerData) - ); -} - -function serializeProviderData(providerData: PossibleProviderData[PN]): string { - return JSON.stringify(providerData); -} - -async function sanitizeProviderData( - providerData: PossibleProviderData[PN], -): Promise { - const data = { - ...providerData, - }; - if (providerDataHasPasswordField(data)) { - data.hashedPassword = await hashPassword(data.hashedPassword); - } - - return data; -} - - -function providerDataHasPasswordField( - providerData: PossibleProviderData[keyof PossibleProviderData], -): providerData is { hashedPassword: string } { - return 'hashedPassword' in providerData; -} - -// PRIVATE API -export function throwInvalidCredentialsError(message?: string): void { - throw new HttpError(401, 'Invalid credentials', { message }) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts deleted file mode 100644 index 637f4203f..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { HttpError } from 'wasp/server'; - -export const PASSWORD_FIELD = 'password'; -const USERNAME_FIELD = 'username'; -const EMAIL_FIELD = 'email'; -const TOKEN_FIELD = 'token'; - -// PUBLIC API -export function ensureValidEmail(args: unknown): void { - validate(args, [ - { validates: EMAIL_FIELD, message: 'email must be present', validator: email => !!email }, - { validates: EMAIL_FIELD, message: 'email must be a valid email', validator: email => isValidEmail(email) }, - ]); -} - -// PUBLIC API -export function ensureValidUsername(args: unknown): void { - validate(args, [ - { validates: USERNAME_FIELD, message: 'username must be present', validator: username => !!username } - ]); -} - -// PUBLIC API -export function ensurePasswordIsPresent(args: unknown): void { - validate(args, [ - { validates: PASSWORD_FIELD, message: 'password must be present', validator: password => !!password }, - ]); -} - -// PUBLIC API -export function ensureValidPassword(args: unknown): void { - validate(args, [ - { validates: PASSWORD_FIELD, message: 'password must be at least 8 characters', validator: password => isMinLength(password, 8) }, - { validates: PASSWORD_FIELD, message: 'password must contain a number', validator: password => containsNumber(password) }, - ]); -} - -// PUBLIC API -export function ensureTokenIsPresent(args: unknown): void { - validate(args, [ - { validates: TOKEN_FIELD, message: 'token must be present', validator: token => !!token }, - ]); -} - -// PRIVATE API -export function throwValidationError(message: string): void { - throw new HttpError(422, 'Validation failed', { message }) -} - -function validate(args: unknown, validators: { validates: string, message: string, validator: (value: unknown) => boolean }[]): void { - for (const { validates, message, validator } of validators) { - if (!validator(args[validates])) { - throwValidationError(message); - } - } -} - -// NOTE(miho): it would be good to replace our custom validations with e.g. Zod - -const validEmailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/ -function isValidEmail(input: unknown): boolean { - if (typeof input !== 'string') { - return false - } - - return input.match(validEmailRegex) !== null -} - -function isMinLength(input: unknown, minLength: number): boolean { - if (typeof input !== 'string') { - return false - } - - return input.length >= minLength -} - -function containsNumber(input: unknown): boolean { - if (typeof input !== 'string') { - return false - } - - return /\d/.test(input) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts deleted file mode 100644 index 8ef076ee1..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { api, handleApiError } from 'wasp/client/api' -import { HttpMethod } from 'wasp/types' -import { - serialize as superjsonSerialize, - deserialize as superjsonDeserialize, - } from 'superjson' - -export type OperationRoute = { method: HttpMethod, path: string } - -export async function callOperation(operationRoute: OperationRoute & { method: HttpMethod.Post }, args: any) { - try { - const superjsonArgs = superjsonSerialize(args) - const response = await api.post(operationRoute.path, superjsonArgs) - return superjsonDeserialize(response.data) - } catch (error) { - handleApiError(error) - } -} - -export function makeOperationRoute(relativeOperationRoute: string): OperationRoute { - return { method: HttpMethod.Post, path: `/${relativeOperationRoute}` } -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts deleted file mode 100644 index fa27d07d0..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { type Expand } from 'wasp/universal/types'; -import { type Request, type Response } from 'express' -import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } from 'express-serve-static-core' -import { prisma } from 'wasp/server' -import { - type User, - type Auth, - type AuthIdentity, -} from "wasp/entities" -import { - type EmailProviderData, - type UsernameProviderData, - type OAuthProviderData, -} from 'wasp/auth/utils' -import { type _Entity } from "./taggedEntities" -import { type Payload } from "./serialization"; - -export * from "./taggedEntities" -export * from "./serialization" - -export type Query = - Operation - -export type Action = - Operation - -export type AuthenticatedQuery = - AuthenticatedOperation - -export type AuthenticatedAction = - AuthenticatedOperation - -type AuthenticatedOperation = ( - args: Input, - context: ContextWithUser, -) => Output | Promise - -export type AuthenticatedApi< - Entities extends _Entity[], - Params extends ExpressParams, - ResBody, - ReqBody, - ReqQuery extends ExpressQuery, - Locals extends Record -> = ( - req: Request, - res: Response, - context: ContextWithUser, -) => void - -type Operation = ( - args: Input, - context: Context, -) => Output | Promise - -export type Api< - Entities extends _Entity[], - Params extends ExpressParams, - ResBody, - ReqBody, - ReqQuery extends ExpressQuery, - Locals extends Record -> = ( - req: Request, - res: Response, - context: Context, -) => void - -type EntityMap = { - [EntityName in Entities[number]["_entityName"]]: PrismaDelegate[EntityName] -} - -export type PrismaDelegate = { - "User": typeof prisma.user, - "Task": typeof prisma.task, -} - -type Context = Expand<{ - entities: Expand> -}> - -type ContextWithUser = Expand & { user?: AuthUser }> - -// TODO: This type must match the logic in auth/session.js (if we remove the -// password field from the object there, we must do the same here). Ideally, -// these two things would live in the same place: -// https://github.com/wasp-lang/wasp/issues/965 - -export type DeserializedAuthIdentity = Expand & { - providerData: Omit | Omit | OAuthProviderData -}> - -export type AuthUser = User & { - auth: Auth & { - identities: DeserializedAuthIdentity[] - } | null -} - -export type { ProviderName } from 'wasp/auth/utils' diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts deleted file mode 100644 index 54c224ba2..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { prisma } from 'wasp/server' - -import { createTask as createTask_ext } from 'wasp/ext-src/task/actions.js' -import { updateTask as updateTask_ext } from 'wasp/ext-src/task/actions.js' -import { deleteTasks as deleteTasks_ext } from 'wasp/ext-src/task/actions.js' -import { send as send_ext } from 'wasp/ext-src/user/customEmailSending.js' - -export type CreateTask = typeof createTask_ext - -export const createTask = async (args, context) => { - return (createTask_ext as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} - -export type UpdateTask = typeof updateTask_ext - -export const updateTask = async (args, context) => { - return (updateTask_ext as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} - -export type DeleteTasks = typeof deleteTasks_ext - -export const deleteTasks = async (args, context) => { - return (deleteTasks_ext as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} - -export type CustomEmailSending = typeof send_ext - -export const customEmailSending = async (args, context) => { - return (send_ext as any)(args, { - ...context, - entities: { - User: prisma.user, - }, - }) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts deleted file mode 100644 index cbfb76d35..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { prisma } from 'wasp/server' - -import { getTasks as getTasks_ext } from 'wasp/ext-src/task/queries.js' - -export type GetTasks = typeof getTasks_ext - -export const getTasks = async (args, context) => { - return (getTasks_ext as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts deleted file mode 100644 index d7fe31499..000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts +++ /dev/null @@ -1,67 +0,0 @@ -import crypto from 'crypto' -import { Request, Response, NextFunction } from 'express' - -import { readdir } from 'fs' -import { dirname } from 'path' -import { fileURLToPath } from 'url' - -import { type AuthUser } from 'wasp/auth' - -type RequestWithExtraFields = Request & { - user?: AuthUser; - sessionId?: string; -} - -/** - * Decorator for async express middleware that handles promise rejections. - * @param {Func} middleware - Express middleware function. - * @returns Express middleware that is exactly the same as the given middleware but, - * if given middleware returns promise, reject of that promise will be correctly handled, - * meaning that error will be forwarded to next(). - */ -export const handleRejection = ( - middleware: ( - req: RequestWithExtraFields, - res: Response, - next: NextFunction - ) => any -) => -async (req: RequestWithExtraFields, res: Response, next: NextFunction) => { - try { - await middleware(req, res, next) - } catch (error) { - next(error) - } -} - -export const sleep = (ms: number): Promise => new Promise((r) => setTimeout(r, ms)) - -export function getDirPathFromFileUrl(fileUrl: string): string { - return fileURLToPath(dirname(fileUrl)) -} - -export async function importJsFilesFromDir( - pathToDir: string, - whitelistedFileNames: string[] | null = null -): Promise { - return new Promise((resolve, reject) => { - readdir(pathToDir, async (err, files) => { - if (err) { - return reject(err) - } - const importPromises = files - .filter((file) => file.endsWith('.js') && isWhitelistedFileName(file)) - .map((file) => import(`${pathToDir}/${file}`)) - resolve(Promise.all(importPromises)) - }) - }) - - function isWhitelistedFileName(fileName: string) { - // No whitelist means all files are whitelisted - if (!Array.isArray(whitelistedFileNames)) { - return true - } - - return whitelistedFileNames.some((whitelistedFileName) => fileName === whitelistedFileName) - } -}