diff --git a/.dockerignore b/.dockerignore index 9e3d11990..d1e5afb1e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,5 @@ **/.dockerignore **/*.yaml .secrets*.yaml +apple +android diff --git a/README.md b/README.md index 1d8f2ad22..2de5a3734 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,11 @@ This will start postgres, initialize the database, and start the web and api ser Open and confirm Omnivore is running -### 3. Create a test account +### 3. Login with the test account -Omnivore uses social login, but for testing there is an email + password -option. +During database setup docker-compose creates an account `demo@omnivore.app`, password: `demo`. -Go to in your browser. +Go to in your browser and choose `Continue with Email` to login. ### Frontend Development diff --git a/packages/api/src/resolvers/save/index.ts b/packages/api/src/resolvers/save/index.ts index d5c2abb29..a25a645b6 100644 --- a/packages/api/src/resolvers/save/index.ts +++ b/packages/api/src/resolvers/save/index.ts @@ -74,7 +74,7 @@ export const saveUrlResolver = authorized< return { errorCodes: [SaveErrorCode.Unauthorized] } } - return (await saveUrl(ctx, user, input)) as SaveSuccess + return (await saveUrl({ ...ctx, uid }, user, input)) as SaveSuccess }) export const saveFileResolver = authorized< diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 8721fb9ca..0b4b5266c 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -54,6 +54,15 @@ import { import { createWebAuthToken } from './jwt_helpers' import { createSsoToken, ssoRedirectURL } from '../../utils/sso' +export interface SignupRequest { + email: string + password: string + name: string + username: string + bio?: string + pictureUrl?: string +} + const logger = buildLogger('app.dispatch') const signToken = promisify(jwt.sign) @@ -62,6 +71,19 @@ const cookieParams = { maxAge: 365 * 24 * 60 * 60 * 1000, } +export const isValidSignupRequest = (obj: any): obj is SignupRequest => { + return ( + 'email' in obj && + obj.email.trim().length > 0 && // email must not be empty + 'password' in obj && + obj.password.length >= 8 && // password must be at least 8 characters + 'name' in obj && + obj.name.trim().length > 0 && // name must not be empty + 'username' in obj && + obj.username.trim().length > 0 // username must not be empty + ) +} + export function authRouter() { const router = express.Router() @@ -443,26 +465,6 @@ export function authRouter() { '/email-signup', cors(corsConfig), async (req: express.Request, res: express.Response) => { - interface SignupRequest { - email: string - password: string - name: string - username: string - bio?: string - pictureUrl?: string - } - function isValidSignupRequest(obj: any): obj is SignupRequest { - return ( - 'email' in obj && - obj.email.trim().length > 0 && // email must not be empty - 'password' in obj && - obj.password.length >= 8 && // password must be at least 8 characters - 'name' in obj && - obj.name.trim().length > 0 && // name must not be empty - 'username' in obj && - obj.username.trim().length > 0 // username must not be empty - ) - } if (!isValidSignupRequest(req.body)) { return res.redirect( `${env.client.url}/auth/email-signup?errorCodes=INVALID_CREDENTIALS` diff --git a/packages/api/src/routers/auth/mobile/mobile_auth_router.ts b/packages/api/src/routers/auth/mobile/mobile_auth_router.ts index c43df9bc8..f27a032fa 100644 --- a/packages/api/src/routers/auth/mobile/mobile_auth_router.ts +++ b/packages/api/src/routers/auth/mobile/mobile_auth_router.ts @@ -32,13 +32,8 @@ export function mobileAuthRouter() { }) router.post('/email-sign-up', async (req, res) => { - const { email, password, username, name } = req.body - const payload = await createMobileEmailSignUpResponse( - email, - password, - username, - name - ) + const payload = await createMobileEmailSignUpResponse(req.body) + res.status(payload.statusCode).json(payload.json) }) diff --git a/packages/api/src/routers/auth/mobile/sign_in.ts b/packages/api/src/routers/auth/mobile/sign_in.ts index a19b505c3..323556569 100644 --- a/packages/api/src/routers/auth/mobile/sign_in.ts +++ b/packages/api/src/routers/auth/mobile/sign_in.ts @@ -1,18 +1,17 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ +import UserModel from '../../../datalayer/user' +import { StatusType } from '../../../datalayer/user/model' +import { getUserByEmail } from '../../../services/create_user' +import { sendConfirmationEmail } from '../../../services/send_emails' +import { comparePassword } from '../../../utils/auth' import { decodeAppleToken } from '../apple_auth' -import { decodeGoogleToken } from '../google_auth' import { + AuthProvider, DecodeTokenResult, JsonResponsePayload, - AuthProvider, } from '../auth_types' +import { decodeGoogleToken } from '../google_auth' import { createMobileAuthPayload } from '../jwt_helpers' -import UserModel from '../../../datalayer/user' -import { initModels } from '../../../server' -import { sendConfirmationEmail } from '../../../services/send_emails' -import { kx } from '../../../datalayer/knex_config' -import { StatusType } from '../../../datalayer/user/model' -import { comparePassword } from '../../../utils/auth' export async function createMobileSignInResponse( isAndroid: boolean, @@ -46,11 +45,7 @@ export async function createMobileEmailSignInResponse( throw new Error('Missing username or password') } - const models = initModels(kx, false) - const user = await models.user.getWhere({ - email, - }) - + const user = await getUserByEmail(email.trim()) if (!user?.id || !user?.password) { throw new Error('user not found') } diff --git a/packages/api/src/routers/auth/mobile/sign_up.ts b/packages/api/src/routers/auth/mobile/sign_up.ts index 55d9e6dea..f21e64d5d 100644 --- a/packages/api/src/routers/auth/mobile/sign_up.ts +++ b/packages/api/src/routers/auth/mobile/sign_up.ts @@ -11,6 +11,7 @@ import { createPendingUserToken, suggestedUsername } from '../jwt_helpers' import UserModel from '../../../datalayer/user' import { hashPassword } from '../../../utils/auth' import { createUser } from '../../../services/create_user' +import { isValidSignupRequest } from '../auth_router' export async function createMobileSignUpResponse( isAndroid: boolean, @@ -45,24 +46,24 @@ export async function createMobileSignUpResponse( } export async function createMobileEmailSignUpResponse( - email?: string, - password?: string, - username?: string, - name?: string + requestBody: any ): Promise { try { - if (!email || !password || !username || !name) { + if (!isValidSignupRequest(requestBody)) { throw new Error('Missing username, password, name, or username') } + const { email, password, name, username } = requestBody + // trim whitespace in email address + const trimmedEmail = email.trim() const hashedPassword = await hashPassword(password) await createUser({ - email, + email: trimmedEmail, provider: 'EMAIL', - sourceUserId: email, - name, - username: username.toLowerCase(), + sourceUserId: trimmedEmail, + name: name.trim(), + username: username.trim().toLowerCase(), password: hashedPassword, pendingConfirmation: true, }) diff --git a/packages/api/src/services/create_user.ts b/packages/api/src/services/create_user.ts index f8e2fbea2..ec180f5be 100644 --- a/packages/api/src/services/create_user.ts +++ b/packages/api/src/services/create_user.ts @@ -24,7 +24,8 @@ export const createUser = async (input: { password?: string pendingConfirmation?: boolean }): Promise<[User, Profile]> => { - const existingUser = await getUserByEmail(input.email) + const trimmedEmail = input.email.trim() + const existingUser = await getUserByEmail(trimmedEmail) if (existingUser) { if (existingUser.profile) { return Promise.reject({ errorCode: SignupErrorCode.UserExists }) @@ -63,7 +64,7 @@ export const createUser = async (input: { const user = await t.getRepository(User).save({ source: input.provider, name: input.name, - email: input.email, + email: trimmedEmail, sourceUserId: input.sourceUserId, password: input.password, status: input.pendingConfirmation diff --git a/packages/web/components/templates/article/Article.tsx b/packages/web/components/templates/article/Article.tsx index 8204bc7d1..411b43b84 100644 --- a/packages/web/components/templates/article/Article.tsx +++ b/packages/web/components/templates/article/Article.tsx @@ -159,6 +159,44 @@ export function Article(props: ArticleProps): JSX.Element { }) }, []) + useEffect(() => { + // Get all images with initial sizes, if they are small + // make sure they get displayed small + const sizedImages = Array.from( + document.querySelectorAll('img[data-omnivore-width]') + ) + + sizedImages.forEach((element) => { + const img = element as HTMLImageElement + const width = Number(img.getAttribute('data-omnivore-width')) + const height = Number(img.getAttribute('data-omnivore-height')) + console.log('width and height: ', width, height) + + if (!isNaN(width) && !isNaN(height) && width < 100 && height < 100) { + img.style.setProperty('width', `${width}px`) + img.style.setProperty('height', `${height}px`) + img.style.setProperty('max-width', 'unset') + } + }) + + const fallbackImages = Array.from( + document.querySelectorAll('img[data-omnivore-original-src]') + ) + + fallbackImages.forEach((element) => { + const img = element as HTMLImageElement + const fallbackSrc = img.getAttribute('data-omnivore-original-src') + if (fallbackSrc) { + img.onerror = () => { + console.log('image falling back to original: ', fallbackSrc) + // If the image fails to load fallback to the original + img.onerror = null + img.src = fallbackSrc + } + } + }) + }, [props.content]) + return ( <> - Get Help + About +
  • + Pricing +
  • +
  • + + Privacy + +
  • Contact us via email
  • diff --git a/packages/web/next.config.js b/packages/web/next.config.js index 8d3c2378a..19fd0aacf 100644 --- a/packages/web/next.config.js +++ b/packages/web/next.config.js @@ -96,6 +96,11 @@ const moduleExports = { destination: '/.well-known/security.txt', permanent: true, }, + { + source: '/privacy', + destination: 'https://docs.omnivore.app/about/privacy-policy', + permanent: true, + }, { source: '/install/chrome', destination: diff --git a/packages/web/pages/privacy.tsx b/packages/web/pages/privacy.tsx deleted file mode 100644 index 6ecdbf013..000000000 --- a/packages/web/pages/privacy.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useRouter } from 'next/router' -import { PrivacyPolicy } from '../components/templates/PrivacyPolicy' -import { SettingsLayout } from '../components/templates/SettingsLayout' - -export default function Privacy(): JSX.Element { - const router = useRouter() - const appEmbedViewQuery = router.query.isAppEmbedView as string | undefined - const isAppEmbedView = (appEmbedViewQuery ?? '').length > 0 - - if (isAppEmbedView) { - return - } else { - return ( - - - - ) - } -}