From f7791850660009beb19d5258f891d6ddba20a82f Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 25 Apr 2023 12:17:24 +0800 Subject: [PATCH 01/14] Do not enlarge small images, fallback to original src if proxy URL fails --- .../components/templates/article/Article.tsx | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) 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 ( <> Date: Tue, 25 Apr 2023 16:35:41 +0800 Subject: [PATCH 02/14] Bump android version --- android/Omnivore/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index b3c8f5ed1..dda1a8cc8 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 46 - versionName "0.0.46" + versionCode 47 + versionName "0.0.47" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 692fd7b959f747a93d291533ea4de81c235ab519 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 25 Apr 2023 17:01:25 +0800 Subject: [PATCH 03/14] Fix save url error when uid is not in the apollo context --- packages/api/src/resolvers/save/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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< From 91335a73155446b479c00ac195a5f4edeaa8f2f5 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 26 Apr 2023 19:32:19 +0800 Subject: [PATCH 04/14] Update login section in local development --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 From 0a3d56ea42fbefa7cd3e3dfdc833aa44d892e0b9 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 26 Apr 2023 22:03:19 +0800 Subject: [PATCH 05/14] Link to new privacy policy --- packages/web/next.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/web/next.config.js b/packages/web/next.config.js index 8d3c2378a..db1d4b245 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', + permanent: true, + }, { source: '/install/chrome', destination: From 881e2ea5954901459186e6ff8bc3ce84ef490957 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 26 Apr 2023 22:03:36 +0800 Subject: [PATCH 06/14] Remove old privacy document --- packages/web/pages/privacy.tsx | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 packages/web/pages/privacy.tsx 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 ( - - - - ) - } -} From 9efe4c53bbd0a21314cbb460a850d719b5ff5f4c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 26 Apr 2023 22:38:09 +0800 Subject: [PATCH 07/14] Update privacy policy URL --- packages/web/next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/next.config.js b/packages/web/next.config.js index db1d4b245..396ed283f 100644 --- a/packages/web/next.config.js +++ b/packages/web/next.config.js @@ -98,7 +98,7 @@ const moduleExports = { }, { source: '/privacy', - destination: 'https://docs.omnivore.app/about/privacy', + destination: 'https://docs.omnivore.app/about/privacy-policy.html', permanent: true, }, { From 160b673bd2a44d677068103f810bc6ec2a1929d5 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 26 Apr 2023 22:49:22 +0800 Subject: [PATCH 08/14] Add links on about pages --- .../web/components/templates/landing/LandingFooter.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/web/components/templates/landing/LandingFooter.tsx b/packages/web/components/templates/landing/LandingFooter.tsx index 1300ff284..c8b41ff73 100644 --- a/packages/web/components/templates/landing/LandingFooter.tsx +++ b/packages/web/components/templates/landing/LandingFooter.tsx @@ -66,8 +66,16 @@ export function LandingFooter(): JSX.Element { - Get Help + About +
  • + Pricing +
  • +
  • + + Privacy + +
  • Contact us via email
  • From 44c2b4c9b052ccc80b46488c2f2c8066492c0c81 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 26 Apr 2023 22:49:39 +0800 Subject: [PATCH 09/14] Update privacy policy link --- packages/web/next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/next.config.js b/packages/web/next.config.js index 396ed283f..19fd0aacf 100644 --- a/packages/web/next.config.js +++ b/packages/web/next.config.js @@ -98,7 +98,7 @@ const moduleExports = { }, { source: '/privacy', - destination: 'https://docs.omnivore.app/about/privacy-policy.html', + destination: 'https://docs.omnivore.app/about/privacy-policy', permanent: true, }, { From e199e6f09838a08dad5f507f4271bac40c42ef24 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 26 Apr 2023 23:17:46 +0800 Subject: [PATCH 10/14] Add more stuff to dockerignore --- .dockerignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.dockerignore b/.dockerignore index 9e3d11990..d1e5afb1e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,5 @@ **/.dockerignore **/*.yaml .secrets*.yaml +apple +android From fb4c8cb1ed18278cb53e2d752460336603883dc7 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 27 Apr 2023 11:33:03 +0800 Subject: [PATCH 11/14] trim user email in mobile sign-up router --- .../api/src/routers/auth/mobile/sign_up.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/api/src/routers/auth/mobile/sign_up.ts b/packages/api/src/routers/auth/mobile/sign_up.ts index 55d9e6dea..350257d55 100644 --- a/packages/api/src/routers/auth/mobile/sign_up.ts +++ b/packages/api/src/routers/auth/mobile/sign_up.ts @@ -1,16 +1,16 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ +import UserModel from '../../../datalayer/user' +import { createUser } from '../../../services/create_user' +import { hashPassword } from '../../../utils/auth' import { decodeAppleToken } from '../apple_auth' -import { decodeGoogleToken } from '../google_auth' import { + AuthProvider, DecodeTokenResult, JsonResponsePayload, - AuthProvider, PendingUserTokenPayload, } from '../auth_types' +import { decodeGoogleToken } from '../google_auth' import { createPendingUserToken, suggestedUsername } from '../jwt_helpers' -import UserModel from '../../../datalayer/user' -import { hashPassword } from '../../../utils/auth' -import { createUser } from '../../../services/create_user' export async function createMobileSignUpResponse( isAndroid: boolean, @@ -55,14 +55,16 @@ export async function createMobileEmailSignUpResponse( throw new Error('Missing username, password, name, or username') } + // 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, }) From 673c43aee0759bc98c0f50229bc1d7052e956714 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 27 Apr 2023 11:37:27 +0800 Subject: [PATCH 12/14] trim user email in mobile sign-in router --- .../api/src/routers/auth/mobile/sign_in.ts | 21 +++++++------------ .../api/src/routers/auth/mobile/sign_up.ts | 10 ++++----- 2 files changed, 13 insertions(+), 18 deletions(-) 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 350257d55..c251558bd 100644 --- a/packages/api/src/routers/auth/mobile/sign_up.ts +++ b/packages/api/src/routers/auth/mobile/sign_up.ts @@ -1,16 +1,16 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ -import UserModel from '../../../datalayer/user' -import { createUser } from '../../../services/create_user' -import { hashPassword } from '../../../utils/auth' import { decodeAppleToken } from '../apple_auth' +import { decodeGoogleToken } from '../google_auth' import { - AuthProvider, DecodeTokenResult, JsonResponsePayload, + AuthProvider, PendingUserTokenPayload, } from '../auth_types' -import { decodeGoogleToken } from '../google_auth' import { createPendingUserToken, suggestedUsername } from '../jwt_helpers' +import UserModel from '../../../datalayer/user' +import { hashPassword } from '../../../utils/auth' +import { createUser } from '../../../services/create_user' export async function createMobileSignUpResponse( isAndroid: boolean, From 0e8887a1d9d4453897224e74a97d94120490e7e2 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 27 Apr 2023 11:39:34 +0800 Subject: [PATCH 13/14] trim user email before saving in db --- packages/api/src/services/create_user.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 From 31cbcc7c7f98552ec00aa527aae8079fee7f3b94 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 27 Apr 2023 12:18:32 +0800 Subject: [PATCH 14/14] validate sign up request body in mobile sign-up router --- packages/api/src/routers/auth/auth_router.ts | 42 ++++++++++--------- .../routers/auth/mobile/mobile_auth_router.ts | 9 +--- .../api/src/routers/auth/mobile/sign_up.ts | 9 ++-- 3 files changed, 28 insertions(+), 32 deletions(-) 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_up.ts b/packages/api/src/routers/auth/mobile/sign_up.ts index c251558bd..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,15 +46,13 @@ 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()