From b3a2fbb42777c7bd0308cb51f196e4d8de9911aa Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Wed, 23 Oct 2024 16:37:21 +0800 Subject: [PATCH] feat: social sign-in and register --- packages/shared/scss/normalized.scss | 4 + .../src/index.ts | 1 + .../src/index.ts | 1 + .../social-sign-in-and-register/.eslintrc.cjs | 25 +++++ .../social-sign-in-and-register/favicon.ico | Bin 0 -> 15406 bytes .../social-sign-in-and-register/index.html | 24 +++++ .../social-sign-in-and-register/package.json | 36 +++++++ .../social-sign-in-and-register/src/consts.ts | 13 +++ .../src/include.d/dom.d.ts | 3 + .../src/include.d/vite-env.d.ts | 1 + .../social-sign-in-and-register/src/index.ts | 97 ++++++++++++++++++ .../social-sign-in-and-register/src/utils.ts | 30 ++++++ .../social-sign-in-and-register/tsconfig.json | 11 ++ pnpm-lock.yaml | 53 +++++++++- 14 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 packages/social-sign-in-and-register/.eslintrc.cjs create mode 100644 packages/social-sign-in-and-register/favicon.ico create mode 100644 packages/social-sign-in-and-register/index.html create mode 100644 packages/social-sign-in-and-register/package.json create mode 100644 packages/social-sign-in-and-register/src/consts.ts create mode 100644 packages/social-sign-in-and-register/src/include.d/dom.d.ts create mode 100644 packages/social-sign-in-and-register/src/include.d/vite-env.d.ts create mode 100644 packages/social-sign-in-and-register/src/index.ts create mode 100644 packages/social-sign-in-and-register/src/utils.ts create mode 100644 packages/social-sign-in-and-register/tsconfig.json diff --git a/packages/shared/scss/normalized.scss b/packages/shared/scss/normalized.scss index 0bcb1ca..3ab8f51 100644 --- a/packages/shared/scss/normalized.scss +++ b/packages/shared/scss/normalized.scss @@ -42,6 +42,10 @@ form { gap: 20px; border-radius: 6px; background-color: var(--color-static-white); + + &.hidden { + display: none; + } } .input-field { diff --git a/packages/sign-in-with-verification-code/src/index.ts b/packages/sign-in-with-verification-code/src/index.ts index 78b0354..38688a4 100644 --- a/packages/sign-in-with-verification-code/src/index.ts +++ b/packages/sign-in-with-verification-code/src/index.ts @@ -123,6 +123,7 @@ window.addEventListener('load', () => { window.location.replace(redirectTo); } catch (error) { handleError(error); + setSubmitLoading(false); } }); }); diff --git a/packages/sign-up-with-verification-code/src/index.ts b/packages/sign-up-with-verification-code/src/index.ts index 2bb8e3d..eaebd37 100644 --- a/packages/sign-up-with-verification-code/src/index.ts +++ b/packages/sign-up-with-verification-code/src/index.ts @@ -122,6 +122,7 @@ window.addEventListener('load', () => { window.location.replace(redirectTo); } catch (error) { handleError(error); + setSubmitLoading(false); } }); }); diff --git a/packages/social-sign-in-and-register/.eslintrc.cjs b/packages/social-sign-in-and-register/.eslintrc.cjs new file mode 100644 index 0000000..57e7f69 --- /dev/null +++ b/packages/social-sign-in-and-register/.eslintrc.cjs @@ -0,0 +1,25 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: '@silverhand/eslint-config', + rules: { + 'jsx-a11y/no-autofocus': 'off', + 'unicorn/prefer-string-replace-all': 'off', + 'no-restricted-syntax': 'off', + '@silverhand/fp/no-mutation': 'off', + '@silverhand/fp/no-let': 'off', + }, + overrides: [ + { + files: ['*.config.js', '*.config.ts', '*.d.ts'], + rules: { + 'import/no-unused-modules': 'off', + }, + }, + { + files: ['*.d.ts'], + rules: { + 'import/no-unassigned-import': 'off', + }, + }, + ], +}; diff --git a/packages/social-sign-in-and-register/favicon.ico b/packages/social-sign-in-and-register/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3cf672d954d2e7a259eefa9d3fecc5a65eebd7e7 GIT binary patch literal 15406 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 W_BYinf7Sh&?|Z-hfA0UD2mS}C&gzo@ literal 0 HcmV?d00001 diff --git a/packages/social-sign-in-and-register/index.html b/packages/social-sign-in-and-register/index.html new file mode 100644 index 0000000..20b4130 --- /dev/null +++ b/packages/social-sign-in-and-register/index.html @@ -0,0 +1,24 @@ + + + + + + Logto experience sample + + + + + + +
+
+ + +
+ +
+ + diff --git a/packages/social-sign-in-and-register/package.json b/packages/social-sign-in-and-register/package.json new file mode 100644 index 0000000..faaee9a --- /dev/null +++ b/packages/social-sign-in-and-register/package.json @@ -0,0 +1,36 @@ +{ + "name": "@logto/experience-sample-social", + "description": "A sample project demonstrates how to use Logto Experience API to build a social sign-in page.", + "author": "Silverhand Inc. ", + "license": "MIT", + "version": "0.0.0", + "type": "module", + "scripts": { + "precommit": "lint-staged", + "start": "vite", + "dev": "logto-tunnel --verbose & vite", + "build": "tsc -b && vite build", + "lint": "eslint --ext .ts src", + "preview": "vite preview" + }, + "devDependencies": { + "@logto/experience-sample-shared": "workspace:^", + "@logto/schemas": "^1.19.0", + "@silverhand/eslint-config": "^6.0.1", + "@silverhand/ts-config": "^6.0.0", + "eslint": "^8.56.0", + "lint-staged": "^15.0.0", + "prettier": "^3.0.0", + "stylelint": "^15.0.0", + "typescript": "^5.5.3", + "vite": "^5.4.6" + }, + "stylelint": { + "extends": "@silverhand/eslint-config/.stylelintrc" + }, + "prettier": "@silverhand/eslint-config/.prettierrc", + "dependencies": { + "js-base64": "^3.7.7", + "superstruct": "^2.0.2" + } +} diff --git a/packages/social-sign-in-and-register/src/consts.ts b/packages/social-sign-in-and-register/src/consts.ts new file mode 100644 index 0000000..9b623ef --- /dev/null +++ b/packages/social-sign-in-and-register/src/consts.ts @@ -0,0 +1,13 @@ +import { object, union, literal, string } from 'superstruct'; + +/** + * Your social connector ID, which can be found in connector details page. + */ +export const connectorId = 'abcdefghijklmnopqrstu'; + +export const socialAccountNotExistErrorDataGuard = object({ + relatedUser: object({ + type: union([literal('email'), literal('phone')]), + value: string(), + }), +}); diff --git a/packages/social-sign-in-and-register/src/include.d/dom.d.ts b/packages/social-sign-in-and-register/src/include.d/dom.d.ts new file mode 100644 index 0000000..36ac3e0 --- /dev/null +++ b/packages/social-sign-in-and-register/src/include.d/dom.d.ts @@ -0,0 +1,3 @@ +interface Body { + json(): Promise; +} diff --git a/packages/social-sign-in-and-register/src/include.d/vite-env.d.ts b/packages/social-sign-in-and-register/src/include.d/vite-env.d.ts new file mode 100644 index 0000000..e058f21 --- /dev/null +++ b/packages/social-sign-in-and-register/src/include.d/vite-env.d.ts @@ -0,0 +1 @@ +import 'vite/client'; diff --git a/packages/social-sign-in-and-register/src/index.ts b/packages/social-sign-in-and-register/src/index.ts new file mode 100644 index 0000000..9945d93 --- /dev/null +++ b/packages/social-sign-in-and-register/src/index.ts @@ -0,0 +1,97 @@ +import { Api } from '@logto/experience-sample-shared/api'; +import { clearError, handleError, setSubmitLoading } from '@logto/experience-sample-shared/utils'; +import { InteractionEvent, type RequestErrorBody } from '@logto/schemas'; +import { validate } from 'superstruct'; + +import '@logto/experience-sample-shared/scss/normalized.scss'; +import { connectorId, socialAccountNotExistErrorDataGuard } from './consts.js'; +import { generateRandomString, parseQueryParameters } from './utils.js'; + +const api = new Api({ baseUrl: window.location.origin }); + +window.addEventListener('load', async () => { + if (window.location.pathname.startsWith(`/callback/${connectorId}`)) { + void handleSocialCallback(); + } else { + document.querySelector('form')?.addEventListener('submit', handleSubmit); + } +}); + +const parseIdentifyUserError = async (error: unknown) => { + if (error instanceof Response) { + const errorBody = await error.json(); + const [_, data] = validate(errorBody.data, socialAccountNotExistErrorDataGuard); + + return { ...errorBody, data }; + } + throw error; +}; + +const handleSubmit = async (event: Event) => { + event.preventDefault(); + setSubmitLoading(true); + clearError(); + + try { + const state = generateRandomString(8); + + await api.experience.initInteraction({ interactionEvent: InteractionEvent.SignIn }); + const { verificationId, authorizationUri } = await api.experience.createSocialVerification( + connectorId, + { + state, + redirectUri: `${window.location.origin}/callback/${connectorId}`, + } + ); + + sessionStorage.setItem('verificationId', verificationId); + sessionStorage.setItem('state', state); + + window.location.assign(authorizationUri); + } catch (error) { + handleError(error); + setSubmitLoading(false); + } +}; + +const handleSocialCallback = async () => { + document.querySelector('form')?.classList.add('hidden'); + const { state, ...connectorData } = parseQueryParameters(window.location.search); + const verificationId = sessionStorage.getItem('verificationId'); + const stateInStorage = sessionStorage.getItem('state'); + + try { + if (!verificationId || !state || state !== stateInStorage) { + throw new Error('Invalid session.'); + } + + await api.experience.verifySocialVerification(connectorId, { + verificationId, + connectorData, + }); + + try { + await api.experience.identifyUser({ verificationId }); + } catch (error) { + const { code, message, data } = await parseIdentifyUserError(error); + if (code === 'user.identity_not_exist' && data?.relatedUser) { + await api.experience.identifyUser({ verificationId, linkSocialIdentity: true }); + return; + } + if (code === 'user.identity_not_exist' && !data?.relatedUser) { + await api.experience.updateInteractionEvent({ + interactionEvent: InteractionEvent.Register, + }); + await api.experience.identifyUser({ verificationId }); + return; + } + throw new Error(message); + } + + const { redirectTo } = await api.experience.submitInteraction(); + window.location.replace(redirectTo); + } catch (error) { + handleError(error); + setSubmitLoading(false); + } +}; diff --git a/packages/social-sign-in-and-register/src/utils.ts b/packages/social-sign-in-and-register/src/utils.ts new file mode 100644 index 0000000..194548e --- /dev/null +++ b/packages/social-sign-in-and-register/src/utils.ts @@ -0,0 +1,30 @@ +import { fromUint8Array } from 'js-base64'; + +export const generateRandomString = (length: number) => { + return fromUint8Array(crypto.getRandomValues(new Uint8Array(length)), true); +}; + +export const parseQueryParameters = (search: string) => { + const searchParameters = new URLSearchParams(search); + + return Object.fromEntries( + [...searchParameters.entries()].map(([key, value]) => [key, decodeURIComponent(value)]) + ); +}; + +export const handleError = (error: unknown) => { + const errorContainer = document.querySelector('.error-message'); + const submitButton = document.querySelector('.submit-button'); + + console.error(error); + if (errorContainer) { + errorContainer.classList.remove('hidden'); + errorContainer.innerHTML = + error instanceof Error + ? error.message + : 'Error occurred. Please check debugger console for details.'; + } + + submitButton?.removeAttribute('disabled'); + submitButton?.classList.remove('loading'); +}; diff --git a/packages/social-sign-in-and-register/tsconfig.json b/packages/social-sign-in-and-register/tsconfig.json new file mode 100644 index 0000000..b962e88 --- /dev/null +++ b/packages/social-sign-in-and-register/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@silverhand/ts-config/tsconfig.base", + "compilerOptions": { + "baseUrl": "./", + "outDir": "dist", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53b17e5..dfc7c98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,6 +183,46 @@ importers: specifier: ^5.4.0 version: 5.4.6(@types/node@22.3.0)(sass-embedded@1.77.8) + packages/social-sign-in-and-register: + dependencies: + js-base64: + specifier: ^3.7.7 + version: 3.7.7 + superstruct: + specifier: ^2.0.2 + version: 2.0.2 + devDependencies: + '@logto/experience-sample-shared': + specifier: workspace:^ + version: link:../shared + '@logto/schemas': + specifier: ^1.19.0 + version: 1.19.0(zod@3.23.8) + '@silverhand/eslint-config': + specifier: ^6.0.1 + version: 6.0.1(eslint@8.57.0)(prettier@3.3.3)(typescript@5.5.4) + '@silverhand/ts-config': + specifier: ^6.0.0 + version: 6.0.0(typescript@5.5.4) + eslint: + specifier: ^8.56.0 + version: 8.57.0 + lint-staged: + specifier: ^15.0.0 + version: 15.2.9 + prettier: + specifier: ^3.0.0 + version: 3.3.3 + stylelint: + specifier: ^15.0.0 + version: 15.11.0(typescript@5.5.4) + typescript: + specifier: ^5.5.3 + version: 5.5.4 + vite: + specifier: ^5.4.6 + version: 5.4.6(@types/node@22.3.0)(sass-embedded@1.77.8) + packages: '@babel/code-frame@7.24.7': @@ -948,7 +988,7 @@ packages: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} confusing-browser-globals@1.0.11: resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} @@ -1802,6 +1842,9 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2664,6 +2707,10 @@ packages: engines: {node: ^14.13.1 || >=16.0.0} hasBin: true + superstruct@2.0.2: + resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} + engines: {node: '>=14.0.0'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -4657,6 +4704,8 @@ snapshots: jiti@1.21.6: {} + js-base64@3.7.7: {} + js-tokens@4.0.0: {} js-types@1.0.0: {} @@ -5520,6 +5569,8 @@ snapshots: - supports-color - typescript + superstruct@2.0.2: {} + supports-color@5.5.0: dependencies: has-flag: 3.0.0