Browse Source

Remove sandbox projects

dependabot/npm_and_yarn/mobile/ansi-regex-4.1.1
Guillaume Vincent 2 years ago
parent
commit
bc56ee052a
87 changed files with 0 additions and 26424 deletions
  1. +0
    -16
      sandbox/lesspass-tld/main.py
  2. +0
    -24
      sandbox/lesspass-web-component/.gitignore
  3. +0
    -69
      sandbox/lesspass-web-component/package.json
  4. +0
    -25
      sandbox/lesspass-web-component/postcss.config.js
  5. BIN
      sandbox/lesspass-web-component/public/favicon.ico
  6. +0
    -40
      sandbox/lesspass-web-component/public/index.html
  7. BIN
      sandbox/lesspass-web-component/public/logo192.png
  8. BIN
      sandbox/lesspass-web-component/public/logo512.png
  9. +0
    -25
      sandbox/lesspass-web-component/public/manifest.json
  10. +0
    -3
      sandbox/lesspass-web-component/public/robots.txt
  11. +0
    -258
      sandbox/lesspass-web-component/src/App.test.tsx
  12. +0
    -120
      sandbox/lesspass-web-component/src/App.tsx
  13. +0
    -55
      sandbox/lesspass-web-component/src/auth/RegisterForm.test.tsx
  14. +0
    -70
      sandbox/lesspass-web-component/src/auth/RegisterForm.tsx
  15. +0
    -20
      sandbox/lesspass-web-component/src/auth/RegisterPage.tsx
  16. +0
    -30
      sandbox/lesspass-web-component/src/auth/SignInForm.test.tsx
  17. +0
    -54
      sandbox/lesspass-web-component/src/auth/SignInForm.tsx
  18. +0
    -22
      sandbox/lesspass-web-component/src/auth/SignInPage.tsx
  19. +0
    -56
      sandbox/lesspass-web-component/src/auth/authApi.test.ts
  20. +0
    -63
      sandbox/lesspass-web-component/src/auth/authSlice.test.ts
  21. +0
    -72
      sandbox/lesspass-web-component/src/auth/authSlice.ts
  22. +0
    -49
      sandbox/lesspass-web-component/src/components/Nav.tsx
  23. +0
    -123
      sandbox/lesspass-web-component/src/global.d.ts
  24. +0
    -15
      sandbox/lesspass-web-component/src/i18n/en.json
  25. +0
    -15
      sandbox/lesspass-web-component/src/i18n/fr.json
  26. +0
    -27
      sandbox/lesspass-web-component/src/i18n/index.ts
  27. +0
    -13
      sandbox/lesspass-web-component/src/i18n/resources.ts
  28. +0
    -48
      sandbox/lesspass-web-component/src/icon.test.ts
  29. +0
    -129
      sandbox/lesspass-web-component/src/icons.ts
  30. +0
    -27
      sandbox/lesspass-web-component/src/index.tsx
  31. +0
    -78
      sandbox/lesspass-web-component/src/passwordGenerator/PasswordGeneratorForm.test.tsx
  32. +0
    -141
      sandbox/lesspass-web-component/src/passwordGenerator/PasswordGeneratorForm.tsx
  33. +0
    -26
      sandbox/lesspass-web-component/src/passwordGenerator/PasswordGeneratorPage.tsx
  34. +0
    -5
      sandbox/lesspass-web-component/src/passwords/PasswordsPage.tsx
  35. +0
    -63
      sandbox/lesspass-web-component/src/passwords/passwordsApi.test.ts
  36. +0
    -72
      sandbox/lesspass-web-component/src/passwords/passwordsSlice.test.ts
  37. +0
    -70
      sandbox/lesspass-web-component/src/passwords/passwordsSlice.ts
  38. +0
    -1
      sandbox/lesspass-web-component/src/react-app-env.d.ts
  39. +0
    -14
      sandbox/lesspass-web-component/src/services/api.ts
  40. +0
    -46
      sandbox/lesspass-web-component/src/services/localStore.test.ts
  41. +0
    -65
      sandbox/lesspass-web-component/src/services/localStore.ts
  42. +0
    -94
      sandbox/lesspass-web-component/src/settings/SettingsPage.test.tsx
  43. +0
    -71
      sandbox/lesspass-web-component/src/settings/SettingsPage.tsx
  44. +0
    -7
      sandbox/lesspass-web-component/src/settings/defaultSettings.ts
  45. +0
    -5
      sandbox/lesspass-web-component/src/setupTests.ts
  46. +0
    -15
      sandbox/lesspass-web-component/src/store.ts
  47. +0
    -8
      sandbox/lesspass-web-component/src/styles/tailwind.css
  48. +0
    -104
      sandbox/lesspass-web-component/src/unlock/MasterPassword.test.tsx
  49. +0
    -72
      sandbox/lesspass-web-component/src/unlock/MasterPassword.tsx
  50. +0
    -51
      sandbox/lesspass-web-component/src/unlock/UnlockPage.tsx
  51. +0
    -1
      sandbox/lesspass-web-component/src/vendor.d.ts
  52. +0
    -12
      sandbox/lesspass-web-component/tailwind.config.js
  53. +0
    -25
      sandbox/lesspass-web-component/tsconfig.json
  54. +0
    -9533
      sandbox/lesspass-web-component/yarn.lock
  55. +0
    -69
      sandbox/lesspass-website/.gitignore
  56. +0
    -14
      sandbox/lesspass-website/Dockerfile
  57. +0
    -1
      sandbox/lesspass-website/gatsby-browser.js
  58. +0
    -33
      sandbox/lesspass-website/gatsby-config.js
  59. +0
    -46
      sandbox/lesspass-website/nginx.conf
  60. +0
    -46
      sandbox/lesspass-website/package.json
  61. +0
    -3
      sandbox/lesspass-website/postcss.config.js
  62. +0
    -1
      sandbox/lesspass-website/src/declaration.d.ts
  63. BIN
      sandbox/lesspass-website/src/images/HowItWorks.png
  64. BIN
      sandbox/lesspass-website/src/images/cli-badge.png
  65. BIN
      sandbox/lesspass-website/src/images/download-on-the-App-Store.png
  66. +0
    -46
      sandbox/lesspass-website/src/images/download-on-the-App-Store.svg
  67. BIN
      sandbox/lesspass-website/src/images/get-amo-badge.png
  68. BIN
      sandbox/lesspass-website/src/images/get-chrome-badge.png
  69. BIN
      sandbox/lesspass-website/src/images/get-it-on-fdroid.png
  70. BIN
      sandbox/lesspass-website/src/images/google-play-badge.png
  71. BIN
      sandbox/lesspass-website/src/images/icon.png
  72. +0
    -6
      sandbox/lesspass-website/src/pages/404.tsx
  73. +0
    -308
      sandbox/lesspass-website/src/pages/index.tsx
  74. +0
    -3
      sandbox/lesspass-website/src/styles/global.css
  75. BIN
      sandbox/lesspass-website/static/favicon.ico
  76. BIN
      sandbox/lesspass-website/static/fonts/fontawesome-webfont.674f50d2.eot
  77. BIN
      sandbox/lesspass-website/static/fonts/fontawesome-webfont.af7ae505.woff2
  78. BIN
      sandbox/lesspass-website/static/fonts/fontawesome-webfont.b06871f2.ttf
  79. BIN
      sandbox/lesspass-website/static/fonts/fontawesome-webfont.fee66e71.woff
  80. +0
    -2671
      sandbox/lesspass-website/static/img/fontawesome-webfont.912ec66d.svg
  81. +0
    -12
      sandbox/lesspass-website/static/lesspass.min.css
  82. +0
    -22
      sandbox/lesspass-website/static/lesspass.min.js
  83. +0
    -1
      sandbox/lesspass-website/static/lesspass.min.js.map
  84. +0
    -7
      sandbox/lesspass-website/tailwind.config.js
  85. +0
    -69
      sandbox/lesspass-website/tsconfig.json
  86. +0
    -10921
      sandbox/lesspass-website/yarn.lock
  87. +0
    -98
      sandbox/test_import.py

+ 0
- 16
sandbox/lesspass-tld/main.py View File

@@ -1,16 +0,0 @@
import csv
from tld import get_tld

tlds = {}
with open('top-1m.csv', newline='') as csvfile:
lignes = csv.reader(csvfile)
for ligne in lignes:
tld = get_tld(ligne[1], fix_protocol=True)
tlds[tld] = tlds.get(tld, 0) + 1

sorted_tlds = []
for w in sorted(tlds, key=tlds.get, reverse=True):
if tlds[w] > 1 and "xn--" not in w:
sorted_tlds.append(w)

print(sorted_tlds)

+ 0
- 24
sandbox/lesspass-web-component/.gitignore View File

@@ -1,24 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build
/src/index.css

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

+ 0
- 69
sandbox/lesspass-web-component/package.json View File

@@ -1,69 +0,0 @@
{
"name": "lesspass-web-component",
"version": "0.1.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.16",
"@fullhuman/postcss-purgecss": "^4.1.3",
"@reduxjs/toolkit": "^1.7.1",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"@types/axios": "^0.14.0",
"@types/i18next": "^13.0.0",
"@types/jest": "^27.4.0",
"@types/node": "^17.0.12",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-i18next": "^8.1.0",
"@types/react-redux": "^7.1.22",
"@types/react-router-dom": "^5.3.3",
"@types/redux-mock-store": "^1.0.3",
"@types/redux-persist": "^4.3.1",
"autoprefixer": "^10.4.2",
"axios": "^0.25.0",
"history": "^5.2.0",
"i18next": "^21.6.9",
"i18next-browser-languagedetector": "^6.1.3",
"jest-environment-jsdom-sixteen": "^2.0.0",
"lesspass": "^9.2.0",
"npm-run-all": "^4.1.5",
"postcss-cli": "^9.1.0",
"postcss-discard-comments": "^5.0.2",
"postcss-import": "^14.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-i18next": "^11.15.3",
"react-redux": "^7.2.6",
"react-router-dom": "^6.2.1",
"react-scripts": "5.0.0",
"redux-mock-store": "^1.5.4",
"redux-persist": "^6.0.0",
"tailwindcss": "^3.0.16",
"ts-jest": "^27.1.3",
"typescript": "~4.5.5"
},
"scripts": {
"start": "npm-run-all --parallel watch:css start:react",
"build": "npm-run-all build:css build:react",
"build:css": "postcss src/styles/tailwind.css --output src/index.css --env production",
"watch:css": "postcss src/styles/tailwind.css --output src/index.css --watch",
"start:react": "react-scripts start",
"build:react": "react-scripts build",
"test": "CI=true react-scripts test --env=jest-environment-jsdom-sixteen",
"test:watch": "react-scripts test --env=jest-environment-jsdom-sixteen",
"eject": "react-scripts eject",
"prettier": "prettier --write \"src/**/*.js\""
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

+ 0
- 25
sandbox/lesspass-web-component/postcss.config.js View File

@@ -1,25 +0,0 @@
const purgecss = require("@fullhuman/postcss-purgecss")({
content: [
"./src/**/*.jsx",
"./src/**/*.tsx",
"./src/**/*.js",
"./src/**/*.ts",
"./public/index.html",
],
defaultExtractor: (content) => {
const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [];
const innerMatches = content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || [];
return broadMatches.concat(innerMatches);
},
});

module.exports = {
plugins: [
require("postcss-import"),
require("tailwindcss"),
require("autoprefixer"),
...(process.env.NODE_ENV === "production"
? [purgecss, require("postcss-discard-comments")({ removeAll: true })]
: []),
],
};

BIN
sandbox/lesspass-web-component/public/favicon.ico View File

Before After

+ 0
- 40
sandbox/lesspass-web-component/public/index.html View File

@@ -1,40 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#3366cc" />
<meta name="description" content="LessPass stateless password manager" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>LessPass</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
sandbox/lesspass-web-component/public/logo192.png View File

Before After
Width: 600  |  Height: 600  |  Size: 6.4 KiB

BIN
sandbox/lesspass-web-component/public/logo512.png View File

Before After
Width: 1600  |  Height: 1600  |  Size: 17 KiB

+ 0
- 25
sandbox/lesspass-web-component/public/manifest.json View File

@@ -1,25 +0,0 @@
{
"short_name": "LessPass",
"name": "LessPass stateless password manager",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#3366cc",
"background_color": "#ffffff"
}

+ 0
- 3
sandbox/lesspass-web-component/public/robots.txt View File

@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow: /

+ 0
- 258
sandbox/lesspass-web-component/src/App.test.tsx View File

@@ -1,258 +0,0 @@
import React from "react";
import { Provider } from "react-redux";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import { createMemoryHistory, MemoryHistory } from "history";
import App from "./App";
import { Router } from "react-router-dom";
import { I18nextProvider } from "react-i18next";
import { initI18n } from "./i18n";
import reduxStore from "./store";
import { fakeLocalStore } from "./services/localStore";

export const InitApp = ({
store,
history = createMemoryHistory(),
}: {
store: Store;
history?: MemoryHistory;
}) => {
return (
<Provider store={reduxStore}>
<Router history={history}>
<I18nextProvider i18n={initI18n(store)}>
<App store={store} />
</I18nextProvider>
</Router>
</Provider>
);
};

beforeAll(() => {
jest.useFakeTimers();
jest.runAllTimers();
});

afterAll(() => {
jest.useRealTimers();
});

test("at startup master-password-input should be visible", async () => {
render(<InitApp store={fakeLocalStore()} />);
const masterPasswordInput = screen.queryByTestId(
"master-password-input"
) as HTMLInputElement;
await waitFor(() => {
expect(masterPasswordInput).toBeInTheDocument();
expect(masterPasswordInput.type).toBe("password");
expect(masterPasswordInput.value).toBe("");
});
});

test("at startup master-password-input should be focused", async () => {
render(<InitApp store={fakeLocalStore()} />);
const masterPasswordInput = screen.queryByTestId(
"master-password-input"
) as HTMLInputElement;
await waitFor(() => {
expect(masterPasswordInput).toBe(document.activeElement);
});
});

test("at startup unlock page is not present if master password is in store", () => {
render(<InitApp store={fakeLocalStore({ masterPassword: "password" })} />);
const passwordGeneratorForm = screen.queryByTestId("password-generator-form");
expect(passwordGeneratorForm).toBeInTheDocument();
});

test("at startup save-master-password-checkbox should be visible", async () => {
render(<InitApp store={fakeLocalStore()} />);
const saveMasterPasswordCheckbox = screen.queryByTestId(
"save-master-password-checkbox"
) as HTMLInputElement;
await waitFor(() => {
expect(saveMasterPasswordCheckbox).toBeInTheDocument();
expect(saveMasterPasswordCheckbox.type).toBe("checkbox");
expect(saveMasterPasswordCheckbox.checked).toBe(true);
});
});

test("hit enter on unlock page save master password in store", async () => {
const store = fakeLocalStore();
expect(store.getItem("masterPassword")).not.toBe("hit enter");
render(<InitApp store={store} />);
const masterPasswordInput = screen.queryByTestId(
"master-password-input"
) as HTMLInputElement;
expect(masterPasswordInput).toBeInTheDocument();
fireEvent.change(masterPasswordInput, { target: { value: "hit enter" } });
fireEvent.submit(masterPasswordInput);
expect(store.getItem("masterPassword")).toBe("hit enter");
await waitFor(() => {
expect(screen.queryByTestId("password-generator-form")).toBeInTheDocument();
});
});

test("click save button and uncheck save-master-password-checkbox don't save it local storage", async () => {
const store = fakeLocalStore();
expect(store.getItem("masterPassword")).toBeNull();
render(<InitApp store={store} />);
const masterPasswordInput = screen.queryByTestId(
"master-password-input"
) as HTMLInputElement;
expect(masterPasswordInput).toBeInTheDocument();
fireEvent.change(masterPasswordInput, {
target: { value: "master password" },
});
const saveMasterPasswordCheckbox = screen.queryByTestId(
"save-master-password-checkbox"
) as HTMLInputElement;
fireEvent.click(saveMasterPasswordCheckbox);
const submitButton = screen.queryByTestId("unlock") as HTMLButtonElement;
fireEvent.click(submitButton);
expect(store.getItem("masterPassword")).toBeNull();
await waitFor(() => {
expect(screen.queryByTestId("password-generator-form")).toBeInTheDocument();
});
});

test("lock button is present if master password in local storage and redirect to root url", async () => {
render(<InitApp store={fakeLocalStore({ masterPassword: "password" })} />);
const lockButton = screen.queryByTestId("lock-button") as HTMLButtonElement;
expect(lockButton).toBeInTheDocument();
fireEvent.click(lockButton);
const masterPasswordInput = screen.queryByTestId(
"master-password-input"
) as HTMLInputElement;
await waitFor(() => {
expect(masterPasswordInput).toBeInTheDocument();
});
});

test("click on unlock with save-master-password-checkbox false save value in localstorage", async () => {
const store = fakeLocalStore();
render(<InitApp store={store} />);
const saveMasterPasswordCheckbox = screen.queryByTestId(
"save-master-password-checkbox"
) as HTMLInputElement;
fireEvent.click(saveMasterPasswordCheckbox);
await waitFor(() => {
expect(saveMasterPasswordCheckbox).toBeInTheDocument();
expect(saveMasterPasswordCheckbox.type).toBe("checkbox");
expect(saveMasterPasswordCheckbox.checked).toBe(false);
expect(store.getItem("settings")).toBeNull();
});
const submitButton = screen.queryByTestId("unlock") as HTMLButtonElement;
fireEvent.click(submitButton);
await waitFor(() => {
expect(store.getItem("settings")).toMatchObject({
saveMasterPassword: false,
});
});
});

test("save-master-password-checkbox is unchecked if user already choose not to save master password", async () => {
render(
<InitApp
store={fakeLocalStore({ settings: { saveMasterPassword: false } })}
/>
);
const saveMasterPasswordCheckbox = screen.queryByTestId(
"save-master-password-checkbox"
) as HTMLInputElement;
await waitFor(() => {
expect(saveMasterPasswordCheckbox).toBeInTheDocument();
expect(saveMasterPasswordCheckbox.type).toBe("checkbox");
expect(saveMasterPasswordCheckbox.checked).toBe(false);
});
});

test("click on unlock with save-master-password-checkbox false and remove master password from localstorage", async () => {
const store = fakeLocalStore({ masterPassword: "password" });
render(<InitApp store={store} />);
const lockButton = screen.queryByTestId("lock-button") as HTMLButtonElement;
fireEvent.click(lockButton);
const saveMasterPasswordCheckbox = screen.queryByTestId(
"save-master-password-checkbox"
) as HTMLInputElement;
fireEvent.click(saveMasterPasswordCheckbox);
const submitButton = screen.queryByTestId("unlock") as HTMLButtonElement;
fireEvent.click(submitButton);
await waitFor(() => {
expect(store.getItem("masterPassword")).toBeNull();
});
});

test("routing pages unauth", async () => {
const history = createMemoryHistory();
render(
<InitApp
history={history}
store={fakeLocalStore({ masterPassword: "password" })}
/>
);
expect(history.location.pathname).toBe("/");
fireEvent.click(screen.queryByText(/lesspass/i) as HTMLLinkElement);
expect(history.location.pathname).toBe("/");
fireEvent.click(screen.queryByTestId("settings-link") as HTMLLinkElement);
expect(history.location.pathname).toBe("/settings");
expect(screen.queryByText(/passwords/i)).toBeNull();
fireEvent.click(screen.queryByTestId("sign-in-link") as HTMLLinkElement);
expect(history.location.pathname).toBe("/signIn");
fireEvent.click(screen.queryByTestId("register-link") as HTMLLinkElement);
expect(history.location.pathname).toBe("/register");
expect(screen.queryByTestId("my-account-link")).toBeNull();
});

test("routing pages auth", async () => {
const history = createMemoryHistory();
render(
<InitApp
history={history}
store={fakeLocalStore({
masterPassword: "password",
access_token: "access_token",
})}
/>
);
expect(history.location.pathname).toBe("/");
fireEvent.click(screen.queryByText(/lesspass/i) as HTMLLinkElement);
expect(history.location.pathname).toBe("/");
fireEvent.click(screen.queryByTestId("settings-link") as HTMLLinkElement);
expect(history.location.pathname).toBe("/settings");
await waitFor(() => {
expect(
screen.queryByText(/passwords/i) as HTMLLinkElement
).toBeInTheDocument();
});
fireEvent.click(screen.queryByText(/passwords/i) as HTMLLinkElement);
expect(history.location.pathname).toBe("/passwords");
expect(screen.queryByTestId("sign-in-link")).toBeNull();
expect(screen.queryByTestId("register-link")).toBeNull();
fireEvent.click(screen.queryByTestId("my-account-link") as HTMLLinkElement);
expect(history.location.pathname).toBe("/my_account");
});

test("test i18n fr", async () => {
render(<InitApp store={fakeLocalStore({ masterPassword: "password" })} />);
expect(screen.queryByText(/se connecter/i)).toBeNull();
fireEvent.click(screen.queryByTestId("settings-link") as HTMLLinkElement);
fireEvent.click(screen.queryByLabelText(/fr/i) as HTMLInputElement);
expect(screen.queryByText(/se connecter/i)).toBeInTheDocument();
fireEvent.click(screen.queryByLabelText(/en/i) as HTMLInputElement);
expect(screen.queryByText(/sign in/i)).toBeInTheDocument();
});

test("password generator page can't be reach if master password is not unlocked", async () => {
const history = createMemoryHistory();
render(<InitApp history={history} store={fakeLocalStore()} />);
const masterPasswordInput = screen.queryByTestId(
"master-password-input"
) as HTMLInputElement;
fireEvent.change(masterPasswordInput, {
target: { value: "p" },
});
fireEvent.click(screen.queryByText(/lesspass/i) as HTMLLinkElement);
await waitFor(() => {
expect(history.location.pathname).toBe("/unlock");
});
});

+ 0
- 120
sandbox/lesspass-web-component/src/App.tsx View File

@@ -1,120 +0,0 @@
import React, { useState, useEffect } from "react";
import "./icons";
import { Switch, Route, useHistory, Redirect } from "react-router-dom";
import PasswordGeneratorPage from "./passwordGenerator/PasswordGeneratorPage";
import UnlockPage from "./unlock/UnlockPage";
import Nav from "./components/Nav";
import RegisterPage from "./auth/RegisterPage";
import SignInPage from "./auth/SignInPage";
import SettingsPage from "./settings/SettingsPage";
import defaultSettings from "./settings/defaultSettings";
import PasswordsPage from "./passwords/PasswordsPage";
import { useDispatch } from "react-redux";
import { signInSuccess, logout } from "./auth/authSlice";
import {
MASTER_PASSWORD_KEY,
SETTINGS_KEY,
ACCESS_TOKEN,
} from "./services/localStore";

function useLocalStore<T>(
key: string,
initialValue: T,
store: Store
): [T, (t: T) => void] {
const [storedValue, setStoredValue] = useState<T>(
(): T => {
try {
const value = (store.getItem(key) as unknown) as T;
return value || initialValue;
} catch (error) {
return initialValue;
}
}
);
const setValue = (value: T) => {
try {
setStoredValue(value);
store.setItem(key, (value as unknown) as Serializable);
} catch (error) {}
};
return [storedValue, setValue];
}

const App = ({ store }: { store: Store }) => {
const dispatch = useDispatch();
const history = useHistory();

const [masterPassword, setMasterPassword] = useState<MasterPassword>(
() => (store.getItem(MASTER_PASSWORD_KEY) as string) || ""
);

const [settings, setSettings] = useLocalStore<Settings>(
SETTINGS_KEY,
defaultSettings,
store
);

useEffect(() => {
const access_token = store.getItem(ACCESS_TOKEN);
if (access_token) {
dispatch(
signInSuccess({
access: access_token,
} as SignInResponsePayload)
);
} else {
dispatch(logout());
}
}, [dispatch, store]);

return (
<div>
<Nav />
<Switch>
<Route exact path="/unlock">
<UnlockPage
settings={settings}
unlock={(newMasterPassword, saveMasterPassword) => {
if (saveMasterPassword) {
store.setItem(MASTER_PASSWORD_KEY, newMasterPassword);
} else {
store.removeItem(MASTER_PASSWORD_KEY);
}
setMasterPassword(newMasterPassword);
setSettings({ ...settings, saveMasterPassword });
history.push("/");
}}
/>
</Route>
<Route exact path="/">
{masterPassword ? (
<PasswordGeneratorPage
masterPassword={masterPassword}
setMasterPassword={setMasterPassword}
/>
) : (
<Redirect to="/unlock" />
)}
</Route>
<Route exact path="/register">
<RegisterPage />
</Route>
<Route exact path="/signIn">
<SignInPage />
</Route>
<Route exact path="/settings">
<SettingsPage
settings={settings}
setSettings={setSettings}
/>
</Route>
<Route exact path="/passwords">
<PasswordsPage />
</Route>
</Switch>
</div>
);
};

export default App;

+ 0
- 55
sandbox/lesspass-web-component/src/auth/RegisterForm.test.tsx View File

@@ -1,55 +0,0 @@
import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import RegisterForm from "./RegisterForm";

it("Registerform on submit", async () => {
const mockOnSubmit = jest.fn();
render(<RegisterForm onSubmit={mockOnSubmit} />);
const signInForm = screen.getByTestId("register-form");
expect(signInForm).not.toBe(null);
const email = screen.getByTestId("email-input");
fireEvent.change(email, {
target: {
value: "contact@example.org",
},
});
const password = screen.getByTestId("password-input");
fireEvent.change(password, {
target: {
value: "password",
},
});
fireEvent.submit(signInForm);
await waitFor(() => {
expect(mockOnSubmit.mock.calls.length).toBe(1);
expect(mockOnSubmit.mock.calls[0][0]).toEqual({
email: "contact@example.org",
password: "password",
});
});
});

it("Registerform on submit is not triggered if use doesn't accept Terms of service", async () => {
const mockOnSubmit = jest.fn();
render(<RegisterForm onSubmit={mockOnSubmit} />);
const signInForm = screen.getByTestId("register-form");
expect(signInForm).not.toBe(null);
const email = screen.getByTestId("email-input");
fireEvent.change(email, {
target: {
value: "contact@example.org",
},
});
const password = screen.getByTestId("password-input");
fireEvent.change(password, {
target: {
value: "password",
},
});
const tou = screen.getByTestId("term-of-use-checkbox");
fireEvent.click(tou);
fireEvent.submit(signInForm);
await waitFor(() => {
expect(mockOnSubmit.mock.calls.length).toBe(0);
});
});

+ 0
- 70
sandbox/lesspass-web-component/src/auth/RegisterForm.tsx View File

@@ -1,70 +0,0 @@
import React, { useState } from "react";

type Credentials = {
email: string;
password: string;
};

type RegisterFormProps = {
id?: string;
onSubmit: (credentials: Credentials) => void;
};

const RegisterForm = ({
id = "register-form",
onSubmit,
}: RegisterFormProps) => {
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [termOfUse, setTermOfUse] = useState(true);

return (
<form
id={id}
data-testid={id}
onSubmit={(event) => {
event.preventDefault();
const credential = {
email,
password,
};
if (termOfUse) {
onSubmit(credential);
}
}}
>
<label htmlFor="email-input">Email</label>
<input
type="email"
id="email-input"
data-testid="email-input"
name="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
<label htmlFor="password-input">Password</label>
<input
type="password"
id="password-input"
data-testid="password-input"
name="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
<input
type="checkbox"
id="term-of-use-checkbox"
data-testid="term-of-use-checkbox"
name="term-of-use"
checked={termOfUse}
onChange={(event) => setTermOfUse(event.target.checked)}
/>
<label htmlFor="term-of-use-checkbox">accept terms of use</label>
<button type="submit" id="register-button" data-testid="register-button">
Create an account
</button>
</form>
);
};

export default RegisterForm;

+ 0
- 20
sandbox/lesspass-web-component/src/auth/RegisterPage.tsx View File

@@ -1,20 +0,0 @@
import React from "react";
import { Link } from "react-router-dom";
import SignInForm from "./SignInForm";
import { useTranslation } from "react-i18next";

const RegisterPage = () => {
const { t } = useTranslation();
return (
<div>
<SignInForm
id="sign-in-form"
onSubmit={(credential) => console.log(credential)}
/>
{t("auth.alreadyhaveanaccount")}
<Link to="/signIn">sign in</Link>
</div>
);
};

export default RegisterPage;

+ 0
- 30
sandbox/lesspass-web-component/src/auth/SignInForm.test.tsx View File

@@ -1,30 +0,0 @@
import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import SignInForm from "./SignInForm";

it("SignInform on submit", async () => {
const mockOnSubmit = jest.fn();
render(<SignInForm onSubmit={mockOnSubmit} />);
const signInForm = screen.getByTestId("sign-in-form");
expect(signInForm).not.toBe(null);
const email = screen.getByTestId("email-input");
fireEvent.change(email, {
target: {
value: "contact@example.org",
},
});
const password = screen.getByTestId("password-input");
fireEvent.change(password, {
target: {
value: "password",
},
});
fireEvent.submit(signInForm);
await waitFor(() => {
expect(mockOnSubmit.mock.calls.length).toBe(1);
expect(mockOnSubmit.mock.calls[0][0]).toEqual({
email: "contact@example.org",
password: "password",
});
});
});

+ 0
- 54
sandbox/lesspass-web-component/src/auth/SignInForm.tsx View File

@@ -1,54 +0,0 @@
import React, { useState } from "react";

type Credentials = {
email: string;
password: string;
};

type SignInFormProps = {
id?: string;
onSubmit: (credentials: Credentials) => void;
};

const SignInForm = ({ id = "sign-in-form", onSubmit }: SignInFormProps) => {
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
return (
<form
id={id}
data-testid={id}
onSubmit={(event) => {
event.preventDefault();
const credential = {
email,
password,
};
onSubmit(credential);
}}
>
<label htmlFor="email-input">Email</label>
<input
type="email"
id="email-input"
data-testid="email-input"
name="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
<label htmlFor="password-input">Password</label>
<input
type="password"
id="password-input"
data-testid="password-input"
name="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
<button type="submit" id="sign-in-button" data-testid="sign-in-button">
Sign In
</button>
</form>
);
};

export default SignInForm;

+ 0
- 22
sandbox/lesspass-web-component/src/auth/SignInPage.tsx View File

@@ -1,22 +0,0 @@
import React from "react";
import { Link } from "react-router-dom";
import SignInForm from "./SignInForm";
import { useTranslation } from "react-i18next";

const SignInPage = () => {
const { t } = useTranslation();
return (
<div>
<SignInForm
id="sign-in-form"
onSubmit={(credential) => console.log(credential)}
/>
{t("auth.donthaveanaccount")}
<Link data-testid="register-link" to="/register">
register
</Link>
</div>
);
};

export default SignInPage;

+ 0
- 56
sandbox/lesspass-web-component/src/auth/authApi.test.ts View File

@@ -1,56 +0,0 @@
import configureMockStore from "redux-mock-store";
import { mocked } from "ts-jest/utils";
import thunk from "redux-thunk";
import * as api from "../services/api";
import {
AuthState,
initialState,
signIn,
signInStart,
signInSuccess,
signInFailure,
} from "./authSlice";

import { AppDispatch } from "../store";

jest.mock("../services/api");

const mockedApi = mocked(api, true);

const mockedStore = configureMockStore<AuthState, AppDispatch>([thunk]);

test("creates both signInStart and signInSuccess when signIn succeeds", async () => {
const requestPayload = {
email: "contact@example.org",
password: "password",
};
const responsePayload = {
access: "access_token",
refresh: "refresh_token",
};
const store = mockedStore(initialState);
mockedApi.signIn.mockResolvedValueOnce(responsePayload);

await store.dispatch(signIn(requestPayload));

const expectedActions = [signInStart(), signInSuccess(responsePayload)];
expect(store.getActions()).toEqual(expectedActions);
});

test("creates both signInStart and signInFailure when signIn fails", async () => {
const requestPayload = {
email: "contact@example.org",
password: "wrong password",
};
const responseError = new Error("Invalid credentials");
const store = mockedStore(initialState);
mockedApi.signIn.mockRejectedValueOnce(responseError);

await store.dispatch(signIn(requestPayload));

const expectedActions = [
signInStart(),
signInFailure({ error: responseError }),
];
expect(store.getActions()).toEqual(expectedActions);
});

+ 0
- 63
sandbox/lesspass-web-component/src/auth/authSlice.test.ts View File

@@ -1,63 +0,0 @@
import reducer, {
initialState,
signInStart,
signInSuccess,
signInFailure,
logout,
selectIsLoading,
selectError,
selectIsAuthenticated,
selectToken,
} from "./authSlice";
import { PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "../store";

test("should return the initial state", () => {
const nextState = initialState;
const result = reducer(undefined, {} as PayloadAction);
expect(result).toEqual(nextState);
});

test("should properly set loading and error state when signInStart", () => {
const nextState = reducer(initialState, signInStart());
const rootState = { auth: nextState } as RootState;
expect(selectIsAuthenticated(rootState)).toEqual(false);
expect(selectIsLoading(rootState)).toEqual(true);
expect(selectError(rootState)).toEqual(null);
});

it("should properly set loading, error and user information when signInSuccess", () => {
const payload = { access: "access_token" };
const nextState = reducer(initialState, signInSuccess(payload));
const rootState = { auth: nextState } as RootState;
expect(selectIsAuthenticated(rootState)).toEqual(true);
expect(selectToken(rootState)).toEqual(payload.access);
expect(selectIsLoading(rootState)).toEqual(false);
expect(selectError(rootState)).toEqual(null);
});

it("should properly set loading, error and remove user information when signInFailure", () => {
const error = new Error("Incorrect password");
const nextState = reducer(
initialState,
signInFailure({ error: error.message })
);
const rootState = { auth: nextState } as RootState;
expect(selectIsAuthenticated(rootState)).toEqual(false);
expect(selectToken(rootState)).toEqual(null);
expect(selectIsLoading(rootState)).toEqual(false);
expect(selectError(rootState)).toEqual(error.message);
});

it("should properly set loading, error and remove user information when token is null", () => {
const payload = { access: "access_token" };
const nextState = reducer(
reducer(initialState, signInSuccess(payload)),
logout()
);
const rootState = { auth: nextState } as RootState;
expect(selectIsAuthenticated(rootState)).toEqual(false);
expect(selectToken(rootState)).toEqual(null);
expect(selectIsLoading(rootState)).toEqual(false);
expect(selectError(rootState)).toEqual(null);
});

+ 0
- 72
sandbox/lesspass-web-component/src/auth/authSlice.ts View File

@@ -1,72 +0,0 @@
import { createSlice, createSelector, PayloadAction } from "@reduxjs/toolkit";
import * as api from "../services/api";
import { RootState, AppDispatch } from "../store";

export type AuthState = {
isLoading: boolean;
token: AccessToken | null;
error: string | null;
};

export const initialState: AuthState = {
isLoading: false,
token: null,
error: null,
};

const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
signInStart(state) {
state.isLoading = true;
state.error = null;
},
signInSuccess(state, action: PayloadAction<SignInResponsePayload>) {
const { access } = action.payload;
state.token = access;
state.isLoading = false;
state.error = null;
},
signInFailure(state, action) {
const { error } = action.payload;
state.isLoading = false;
state.token = null;
state.error = error;
},
logout(state) {
state.isLoading = false;
state.token = null;
state.error = null;
},
},
});

export const {
signInStart,
signInSuccess,
signInFailure,
logout,
} = authSlice.actions;

export default authSlice.reducer;

export const selectToken = (state: RootState) => state.auth.token;
export const selectError = (state: RootState) => state.auth.error;
export const selectIsLoading = (state: RootState) => state.auth.isLoading;
export const selectIsAuthenticated = createSelector(
[selectToken],
(token) => token !== null
);

export const signIn = ({ email, password }: SignInRequestPayload) => async (
dispatch: AppDispatch
) => {
try {
dispatch(signInStart());
const tokens = await api.signIn({ email, password });
dispatch(signInSuccess(tokens));
} catch (error) {
dispatch(signInFailure({ error }));
}
};

+ 0
- 49
sandbox/lesspass-web-component/src/components/Nav.tsx View File

@@ -1,49 +0,0 @@
import React from "react";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";
import { selectIsAuthenticated } from "../auth/authSlice";

const Nav = () => {
const { t } = useTranslation();
const isAuthenticated = useSelector(selectIsAuthenticated);
if (isAuthenticated) {
return (
<nav>
<ul>
<li>
<Link to="/">LessPass</Link>
</li>
<li>
<Link data-testid="password-link" to="/passwords">{t("nav.passwords")}</Link>
</li>
<li>
<Link data-testid="settings-link" to="/settings">
{t("nav.settings")}
</Link>
</li>
<li>
<Link data-testid="my-account-link" to="/my_account">{t("nav.myaccount")}</Link>
</li>
</ul>
</nav>
);
}
return (
<nav>
<ul>
<li>
<Link to="/">LessPass</Link>
</li>
<li>
<Link data-testid="settings-link" to="/settings">{t("nav.settings")}</Link>
</li>
<li>
<Link data-testid="sign-in-link" to="/signIn">{t("nav.signin")}</Link>
</li>
</ul>
</nav>
);
};

export default Nav;

+ 0
- 123
sandbox/lesspass-web-component/src/global.d.ts View File

@@ -1,123 +0,0 @@
type Serializable = object | string | boolean;

type Store = {
getItem(key: string): Serializable | null;
setItem(key: string, value: Serializable): void;
removeItem(key: string): void;
};

type MasterPassword = string;

type Settings = {
saveMasterPassword: boolean;
useMasterPasswordForAuth: boolean;
language: string | null;
};

type PasswordProfile = {
site: string;
login: string;
lowercase: boolean;
uppercase: boolean;
digits: boolean;
symbols: boolean;
length: number;
counter: number;
};

type LegacyPasswordProfile = {
id:string;
site: string;
login: string;
lowercase: boolean;
uppercase: boolean;
numbers: boolean;
symbols: boolean;
length: number;
counter: number;
};

type FingerprintColor =
| "#000000"
| "#074750"
| "#009191"
| "#FF6CB6"
| "#FFB5DA"
| "#490092"
| "#006CDB"
| "#B66DFF"
| "#6DB5FE"
| "#B5DAFE"
| "#920000"
| "#924900"
| "#DB6D00"
| "#24FE23";

type FingerprintIcon =
| "fa-hashtag"
| "fa-heart"
| "fa-hotel"
| "fa-university"
| "fa-plug"
| "fa-ambulance"
| "fa-bus"
| "fa-car"
| "fa-plane"
| "fa-rocket"
| "fa-ship"
| "fa-subway"
| "fa-truck"
| "fa-jpy"
| "fa-eur"
| "fa-btc"
| "fa-usd"
| "fa-gbp"
| "fa-archive"
| "fa-area-chart"
| "fa-bed"
| "fa-beer"
| "fa-bell"
| "fa-binoculars"
| "fa-birthday-cake"
| "fa-bomb"
| "fa-briefcase"
| "fa-bug"
| "fa-camera"
| "fa-cart-plus"
| "fa-certificate"
| "fa-coffee"
| "fa-cloud"
| "fa-comment"
| "fa-cube"
| "fa-cutlery"
| "fa-database"
| "fa-diamond"
| "fa-exclamation-circle"
| "fa-eye"
| "fa-flag"
| "fa-flask"
| "fa-futbol-o"
| "fa-gamepad"
| "fa-graduation-cap";

type Finger = {
icon: FingerprintIcon;
color: FingerprintColor;
};

type Fingerprint = [Finger, Finger, Finger];

type AccessToken = string;

type SignInRequestPayload = {
email: string;
password: string;
};

type SignInResponsePayload = {
access: AccessToken;
};

type GetPasswordsResponsePayload = {
results: LegacyPasswordProfile[];
};

+ 0
- 15
sandbox/lesspass-web-component/src/i18n/en.json View File

@@ -1,15 +0,0 @@
{
"nav": {
"settings": "settings",
"passwords": "passwords",
"signin": "sign in",
"myaccount": "my account"
},
"settings": {
"selectlanguage": "select default language"
},
"auth": {
"donthaveanaccount": "don't have an account?",
"alreadyhaveanaccount": "already have an account?"
}
}

+ 0
- 15
sandbox/lesspass-web-component/src/i18n/fr.json View File

@@ -1,15 +0,0 @@
{
"nav": {
"settings": "options",
"passwords": "mots de passe",
"signin": "se connecter",
"myaccount": "mon compte"
},
"settings": {
"selectlanguage": "selectionnez la langue par défault"
},
"auth": {
"donthaveanaccount": "pas encore de compte?",
"alreadyhaveanaccount": "vous avez déjà un compte?"
}
}

+ 0
- 27
sandbox/lesspass-web-component/src/i18n/index.ts View File

@@ -1,27 +0,0 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import resources from "./resources";
import { SETTINGS_KEY } from "../services/localStore";
import defaultSettings from "../settings/defaultSettings";

export function initI18n(store: Store) {
const lang = window.navigator.languages
? window.navigator.languages[0]
: window.navigator.language;
let shortLang = lang;
if (shortLang.indexOf("-") !== -1) shortLang = shortLang.split("-")[0];
if (shortLang.indexOf("_") !== -1) shortLang = shortLang.split("_")[0];

const settings = (store.getItem(SETTINGS_KEY) as Settings) || defaultSettings;
const { language } = settings;

i18n.use(initReactI18next).init({
resources,
lng: language || shortLang,
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
});
return i18n;
}

+ 0
- 13
sandbox/lesspass-web-component/src/i18n/resources.ts View File

@@ -1,13 +0,0 @@
import en_translation from "./en.json";
import fr_translation from "./fr.json";

const resources = {
en: {
translation: en_translation,
},
fr: {
translation: fr_translation,
},
};

export default resources;

+ 0
- 48
sandbox/lesspass-web-component/src/icon.test.ts View File

@@ -1,48 +0,0 @@
import { getIconLookup } from "./icons";

test("getIconLookup", async () => {
expect(getIconLookup("fa-flask")).toEqual({
prefix: "fas",
iconName: "flask",
});
expect(getIconLookup("fa-birthday-cake")).toEqual({
prefix: "fas",
iconName: "birthday-cake",
});
});

test("getIconLookup retro compatibility with fa4 icons", async () => {
expect(getIconLookup("fa-jpy")).toEqual({
prefix: "fas",
iconName: "yen-sign",
});
expect(getIconLookup("fa-eur")).toEqual({
prefix: "fas",
iconName: "euro-sign",
});
expect(getIconLookup("fa-usd")).toEqual({
prefix: "fas",
iconName: "dollar-sign",
});
expect(getIconLookup("fa-gbp")).toEqual({
prefix: "fas",
iconName: "pound-sign",
});
expect(getIconLookup("fa-area-chart")).toEqual({
prefix: "fas",
iconName: "chart-area",
});
expect(getIconLookup("fa-cutlery")).toEqual({
prefix: "fas",
iconName: "utensils",
});
expect(getIconLookup("fa-diamond")).toEqual({
prefix: "fas",
iconName: "gem",
});
expect(getIconLookup("fa-futbol-o")).toEqual({
prefix: "fas",
iconName: "futbol",
});
expect(getIconLookup("fa-btc")).toEqual({ prefix: "fab", iconName: "btc" });
});

+ 0
- 129
sandbox/lesspass-web-component/src/icons.ts View File

@@ -1,129 +0,0 @@
import {
library,
IconName,
IconLookup,
IconDefinition,
findIconDefinition,
} from "@fortawesome/fontawesome-svg-core";
import { faBtc } from "@fortawesome/free-brands-svg-icons";
import {
faHashtag,
faHeart,
faHotel,
faUniversity,
faPlug,
faAmbulance,
faBus,
faCar,
faPlane,
faRocket,
faShip,
faSubway,
faTruck,
faYenSign,
faEuroSign,
faDollarSign,
faPoundSign,
faArchive,
faChartArea,
faBed,
faBeer,
faBell,
faBinoculars,
faBirthdayCake,
faBomb,
faBriefcase,
faBug,
faCamera,
faCartPlus,
faCertificate,
faCoffee,
faCloud,
faComment,
faCube,
faUtensils,
faDatabase,
faGem,
faExclamationCircle,
faEye,
faFlag,
faFlask,
faFutbol,
faGamepad,
faGraduationCap,
} from "@fortawesome/free-solid-svg-icons";

library.add(
faHashtag,
faHeart,
faHotel,
faUniversity,
faPlug,
faAmbulance,
faBus,
faCar,
faPlane,
faRocket,
faShip,
faSubway,
faTruck,
faYenSign,
faEuroSign,
faBtc,
faDollarSign,
faPoundSign,
faArchive,
faChartArea,
faBed,
faBeer,
faBell,
faBinoculars,
faBirthdayCake,
faBomb,
faBriefcase,
faBug,
faCamera,
faCartPlus,
faCertificate,
faCoffee,
faCloud,
faCoffee,
faComment,
faCube,
faUtensils,
faDatabase,
faGem,
faExclamationCircle,
faEye,
faFlag,
faFlask,
faFutbol,
faGamepad,
faGraduationCap
);

export function getIconLookup(old_name: FingerprintIcon): IconLookup {
const incompatible_icon_names: Partial<Record<
FingerprintIcon,
IconLookup
>> = {
"fa-jpy": { prefix: "fas", iconName: "yen-sign" },
"fa-eur": { prefix: "fas", iconName: "euro-sign" },
"fa-usd": { prefix: "fas", iconName: "dollar-sign" },
"fa-gbp": { prefix: "fas", iconName: "pound-sign" },
"fa-btc": { prefix: "fab", iconName: "btc" },
"fa-area-chart": { prefix: "fas", iconName: "chart-area" },
"fa-cutlery": { prefix: "fas", iconName: "utensils" },
"fa-diamond": { prefix: "fas", iconName: "gem" },
"fa-futbol-o": { prefix: "fas", iconName: "futbol" },
};
if (Object.keys(incompatible_icon_names).includes(old_name)) {
return incompatible_icon_names[old_name] as IconLookup;
}
const [, ...rest] = old_name.split("-");
return { prefix: "fas", iconName: rest.join("-") as IconName };
}

export function getIcon(iconName: FingerprintIcon): IconDefinition {
return findIconDefinition(getIconLookup(iconName));
}

+ 0
- 27
sandbox/lesspass-web-component/src/index.tsx View File

@@ -1,27 +0,0 @@
import React, { Suspense } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
//import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import { I18nextProvider } from "react-i18next";
import { initI18n } from "./i18n";
import reduxStore from "./store";
import initLocalStore from "./services/localStore";

const localStore = initLocalStore(window.localStorage);

ReactDOM.render(
<React.StrictMode>
<Suspense fallback="...">
<Provider store={reduxStore}>
<BrowserRouter>
<I18nextProvider i18n={initI18n(localStore)}>
<App store={localStore} />
</I18nextProvider>
</BrowserRouter>
</Provider>
</Suspense>
</React.StrictMode>,
document.getElementById("root")
);

+ 0
- 78
sandbox/lesspass-web-component/src/passwordGenerator/PasswordGeneratorForm.test.tsx View File

@@ -1,78 +0,0 @@
import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import PasswordGeneratorForm from "./PasswordGeneratorForm";

test("PasswordGeneratorForm", async () => {
const onPasswordGenerated = jest.fn();
render(
<PasswordGeneratorForm
masterPassword="password"
onPasswordGenerated={onPasswordGenerated}
/>
);

const siteInput = screen.queryByTestId("site-input") as HTMLInputElement;
expect(siteInput.type).toBe("text");
expect(siteInput.value).toBe("");
fireEvent.change(siteInput, {
target: { value: "www.lesspass.com" },
});

const loginInput = screen.queryByTestId("login-input") as HTMLInputElement;
expect(loginInput.type).toBe("text");
expect(loginInput.value).toBe("");
fireEvent.change(loginInput, {
target: { value: "contact@lesspass.com" },
});

const uppercaseCheckbox = screen.queryByTestId(
"uppercase-checkbox"
) as HTMLInputElement;
fireEvent.click(uppercaseCheckbox);

const digitsCheckbox = screen.queryByTestId(
"digits-checkbox"
) as HTMLInputElement;
fireEvent.click(digitsCheckbox);

const symbolsCheckbox = screen.queryByTestId(
"symbols-checkbox"
) as HTMLInputElement;
fireEvent.click(symbolsCheckbox);

const lengthInput = screen.queryByTestId("length-input") as HTMLInputElement;
expect(lengthInput.type).toBe("number");
expect(lengthInput.value).toBe("16");
fireEvent.change(lengthInput, {
target: { value: "17" },
});

const counterInput = screen.queryByTestId(
"counter-input"
) as HTMLInputElement;
expect(counterInput.type).toBe("number");
expect(counterInput.value).toBe("1");
fireEvent.change(counterInput, {
target: { value: "2" },
});

const generatePasswordButton = screen.queryByTestId(
"generate-password-button"
) as HTMLInputElement;
fireEvent.click(generatePasswordButton);

await waitFor(() =>
expect(onPasswordGenerated).toHaveBeenCalledWith("sfexxbwympdakofqp")
);
});

test("site field is selected when we mount the password generator form", () => {
render(
<PasswordGeneratorForm
masterPassword="password"
onPasswordGenerated={jest.fn()}
/>
);
const siteInput = screen.queryByTestId("site-input") as HTMLInputElement;
expect(siteInput).toBe(document.activeElement);
});

+ 0
- 141
sandbox/lesspass-web-component/src/passwordGenerator/PasswordGeneratorForm.tsx View File

@@ -1,141 +0,0 @@
import React, { useState, useRef, useEffect } from "react";
const LessPass = require("lesspass");

const PasswordGeneratorForm = ({
masterPassword,
onPasswordGenerated,
}: {
masterPassword: MasterPassword;
onPasswordGenerated: (generatedPassword: string) => void;
}) => {
const [site, setSite] = useState("");
const [login, setLogin] = useState("");

const [lowercase, setLowercase] = useState(true);
const [uppercase, setUppercase] = useState(true);
const [digits, setDigits] = useState(true);
const [symbols, setSymbols] = useState(true);

const [length, setLength] = useState(16);
const [counter, setCounter] = useState(1);

const siteInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
siteInputRef.current?.focus();
}, []);

return (
<form
id="password-generator-form"
data-testid="password-generator-form"
onSubmit={(event) => {
event.preventDefault();
const passwordProfile = {
site,
login,
lowercase,
uppercase,
digits,
symbols,
length,
counter,
};
LessPass.generatePassword(passwordProfile, masterPassword).then(
onPasswordGenerated
);
}}
>
<label htmlFor="site-input">Site</label>
<input
type="text"
id="site-input"
data-testid="site-input"
name="site"
ref={siteInputRef}
value={site}
onChange={(event) => setSite(event.target.value)}
/>
<label htmlFor="login-input">Last name</label>
<input
type="text"
id="login-input"
data-testid="login-input"
name="login"
value={login}
onChange={(event) => setLogin(event.target.value)}
/>
<fieldset>
<legend>Options</legend>
<input
type="checkbox"
id="lowercase-checkbox"
data-testid="lowercase-checkbox"
name="lowercase"
checked={lowercase}
onChange={(event) => setLowercase(event.target.checked)}
/>
<label htmlFor="lowercase-checkbox">a-z</label>

<input
type="checkbox"
id="uppercase-checkbox"
data-testid="uppercase-checkbox"
name="uppercase"
checked={uppercase}
onChange={(event) => setUppercase(event.target.checked)}
/>
<label htmlFor="uppercase-checkbox">A-Z</label>

<input
type="checkbox"
id="digits-checkbox"
data-testid="digits-checkbox"
name="digits"
checked={digits}
onChange={(event) => setDigits(event.target.checked)}
/>
<label htmlFor="digits-checkbox">0-9</label>

<input
type="checkbox"
id="symbols-checkbox"
data-testid="symbols-checkbox"
name="symbols"
checked={symbols}
onChange={(event) => setSymbols(event.target.checked)}
/>
<label htmlFor="symbols-checkbox">%!@</label>

<label htmlFor="length-input">Length</label>
<input
type="number"
id="length-input"
data-testid="length-input"
name="length"
value={length}
onChange={(event) => setLength(parseInt(event.target.value))}
/>

<label htmlFor="length-input">Counter</label>
<input
type="number"
id="counter-input"
data-testid="counter-input"
name="counter"
value={counter}
onChange={(event) => setCounter(parseInt(event.target.value))}
/>
</fieldset>

<button
type="submit"
id="generate-password-button"
data-testid="generate-password-button"
>
Generate Password
</button>
</form>
);
};

export default PasswordGeneratorForm;

+ 0
- 26
sandbox/lesspass-web-component/src/passwordGenerator/PasswordGeneratorPage.tsx View File

@@ -1,26 +0,0 @@
import React from "react";
import PasswordGeneratorForm from "./PasswordGeneratorForm";

const PasswordGeneratorPage = ({
masterPassword,
setMasterPassword,
}: {
setMasterPassword: (masterPassword: MasterPassword) => void;
masterPassword: MasterPassword;
}) => (
<>
<PasswordGeneratorForm
masterPassword={masterPassword}
onPasswordGenerated={(generatedPassword) => alert(generatedPassword)}
/>
<button
id="lock-button"
data-testid="lock-button"
onClick={() => setMasterPassword("")}
>
lock
</button>
</>
);

export default PasswordGeneratorPage;

+ 0
- 5
sandbox/lesspass-web-component/src/passwords/PasswordsPage.tsx View File

@@ -1,5 +0,0 @@
import React from "react";

const PasswordsPage = () => <div>PasswordsPage</div>;

export default PasswordsPage;

+ 0
- 63
sandbox/lesspass-web-component/src/passwords/passwordsApi.test.ts View File

@@ -1,63 +0,0 @@
import configureMockStore from "redux-mock-store";
import { mocked } from "ts-jest/utils";
import thunk from "redux-thunk";
import * as api from "../services/api";

import {
PasswordsState,
initialState,
getPasswords,
getPasswordsStart,
getPasswordsSuccess,
getPasswordsFailure,
} from "./passwordsSlice";

import { AppDispatch } from "../store";

jest.mock("../services/api");

const mockedApi = mocked(api, true);

const mockedStore = configureMockStore<PasswordsState, AppDispatch>([thunk]);

test("creates both getPasswordsStart and getPasswordsSuccess when getPasswords succeeds", async () => {
const responsePayload = {
results: [
{
id: "p1",
site: "lesspass.com",
login: "contact@lesspass.com",
lowercase: true,
uppercase: true,
numbers: true,
symbols: true,
length: 16,
counter: 1,
},
],
};
const store = mockedStore(initialState);
mockedApi.getPasswords.mockResolvedValueOnce(responsePayload);

await store.dispatch(getPasswords());

const expectedActions = [
getPasswordsStart(),
getPasswordsSuccess(responsePayload),
];
expect(store.getActions()).toEqual(expectedActions);
});

test("creates both getPasswordsStart and getPasswordsFailure when getPasswords fails", async () => {
const responseError = new Error("Invalid credentials");
const store = mockedStore(initialState);
mockedApi.getPasswords.mockRejectedValueOnce(responseError);

await store.dispatch(getPasswords());

const expectedActions = [
getPasswordsStart(),
getPasswordsFailure({ error: responseError }),
];
expect(store.getActions()).toEqual(expectedActions);
});

+ 0
- 72
sandbox/lesspass-web-component/src/passwords/passwordsSlice.test.ts View File

@@ -1,72 +0,0 @@
import reducer, {
initialState,
getPasswordsStart,
getPasswordsSuccess,
getPasswordsFailure,
selectIsLoading,
selectError,
selectPasswords,
} from "./passwordsSlice";
import { PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "../store";

test("should return the initial state", () => {
const nextState = initialState;
const result = reducer(undefined, {} as PayloadAction);
expect(result).toEqual(nextState);
});

test("should properly set loading and error state when getPasswordsStart", () => {
const nextState = reducer(initialState, getPasswordsStart());
const rootState = { passwords: nextState } as RootState;
expect(selectPasswords(rootState)).toEqual([]);
expect(selectIsLoading(rootState)).toEqual(true);
expect(selectError(rootState)).toEqual(null);
});

it("should properly set loading, error and state when getPasswordsSuccess", () => {
const payload = {
results: [
{
id: "p1",
site: "lesspass.com",
login: "contact@lesspass.com",
lowercase: true,
uppercase: true,
numbers: true,
symbols: true,
length: 16,
counter: 1,
},
],
};
const nextState = reducer(initialState, getPasswordsSuccess(payload));
const rootState = { passwords: nextState } as RootState;
expect(selectPasswords(rootState)).toEqual([
{
id: "p1",
site: "lesspass.com",
login: "contact@lesspass.com",
lowercase: true,
uppercase: true,
digits: true,
symbols: true,
length: 16,
counter: 1,
},
]);
expect(selectIsLoading(rootState)).toEqual(false);
expect(selectError(rootState)).toEqual(null);
});

it("should properly set loading, error and state getPasswordsFailure", () => {
const error = new Error("Incorrect password");
const nextState = reducer(
initialState,
getPasswordsFailure({ error: error.message })
);
const rootState = { passwords: nextState } as RootState;
expect(selectPasswords(rootState)).toEqual([]);
expect(selectIsLoading(rootState)).toEqual(false);
expect(selectError(rootState)).toEqual(error.message);
});

+ 0
- 70
sandbox/lesspass-web-component/src/passwords/passwordsSlice.ts View File

@@ -1,70 +0,0 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import * as api from "../services/api";
import { RootState, AppDispatch } from "../store";

export type PasswordsState = {
isLoading: boolean;
results: PasswordProfile[];
error: string | null;
};

export const initialState: PasswordsState = {
isLoading: false,
results: [],
error: null,
};

const passwordsSlice = createSlice({
name: "passwords",
initialState,
reducers: {
getPasswordsStart(state) {
state.isLoading = true;
state.error = null;
},
getPasswordsSuccess(state, action: PayloadAction<GetPasswordsResponsePayload>) {
const { results } = action.payload;
state.isLoading = false;
state.results = results.map((r) => ({
id: r.id,
site: r.site,
login: r.login,
lowercase: r.lowercase,
uppercase: r.uppercase,
digits: r.numbers,
symbols: r.symbols,
length: r.length,
counter: r.counter,
}));
state.error = null;
},
getPasswordsFailure(state, action) {
const { error } = action.payload;
state.isLoading = false;
state.results = [];
state.error = error;
},
},
});

export const {
getPasswordsStart,
getPasswordsSuccess,
getPasswordsFailure,
} = passwordsSlice.actions;

export default passwordsSlice.reducer;

export const selectPasswords = (state: RootState) => state.passwords.results;
export const selectError = (state: RootState) => state.passwords.error;
export const selectIsLoading = (state: RootState) => state.passwords.isLoading;

export const getPasswords = () => async (dispatch: AppDispatch) => {
try {
dispatch(getPasswordsStart());
const results = await api.getPasswords();
dispatch(getPasswordsSuccess(results));
} catch (error) {
dispatch(getPasswordsFailure({ error }));
}
};

+ 0
- 1
sandbox/lesspass-web-component/src/react-app-env.d.ts View File

@@ -1 +0,0 @@
/// <reference types="react-scripts" />

+ 0
- 14
sandbox/lesspass-web-component/src/services/api.ts View File

@@ -1,14 +0,0 @@
import axios from "axios";

export function signIn(payload: SignInRequestPayload): Promise<SignInResponsePayload> {
return axios
.post("https://api.lesspass.com/api/auth/jwt/create/", payload)
.then((response) => response.data);
}

export function getPasswords(): Promise<GetPasswordsResponsePayload> {
return axios
.get("https://api.lesspass.com/api/passwords/")
.then((response) => response.data.results);
}


+ 0
- 46
sandbox/lesspass-web-component/src/services/localStore.test.ts View File

@@ -1,46 +0,0 @@
import { fakeLocalStore } from "./localStore";

test("can save an retrieve string", () => {
const localStorage = fakeLocalStore();
localStorage.setItem("test", "string");
expect(localStorage.getItem("test")).toBe("string");
});

test("can save an delete string", () => {
const localStorage = fakeLocalStore();
localStorage.setItem("test", "string");
expect(localStorage.getItem("test")).toBe("string");
localStorage.removeItem("test");
expect(localStorage.getItem("test")).toBeNull();
});

test("remove non existant string didnt raise an exception", () => {
const localStorage = fakeLocalStore();
localStorage.removeItem("test");
});

test("can initialize local storage with string", () => {
const localStorage = fakeLocalStore({ test: "string" });
expect(localStorage.getItem("test")).toBe("string");
});

test("can save an retrieve object", () => {
const localStorage = fakeLocalStore();
localStorage.setItem("test", { hello: "world" });
expect(localStorage.getItem("test")).toEqual({ hello: "world" });
});

test("can initialize local storage with object and string", () => {
const localStorage = fakeLocalStore({
a: "string",
b: { hello: "world" },
});
expect(localStorage.getItem("a")).toBe("string");
expect(localStorage.getItem("b")).toEqual({ hello: "world" });
});

test("can save an retrieve boolean", () => {
const localStorage = fakeLocalStore();
localStorage.setItem("test", false);
expect(localStorage.getItem("test")).toBe(false);
});

+ 0
- 65
sandbox/lesspass-web-component/src/services/localStore.ts View File

@@ -1,65 +0,0 @@
export const MASTER_PASSWORD_KEY = "masterPassword";
export const SETTINGS_KEY = "settings";
export const ACCESS_TOKEN = "access_token";

export const fakeLocalStore = (initialStore?: {}) => {
class FakeLocalStore implements Store {
store: {
[key: string]: string | object | boolean;
};

constructor(initialStore = {}) {
this.store = initialStore;
}

getItem(key: string) {
if (key in this.store) return this.store[key];
return null;
}

setItem(key: string, value: string | object | boolean) {
this.store[key] = value;
}

removeItem(key: string) {
delete this.store[key];
}
}

return new FakeLocalStore(initialStore);
};

export default function initLocalStore(localStorage: Storage) {
class LocalStore implements Store {
localStorage: Storage;

constructor(initialStore: Storage) {
this.localStorage = initialStore;
}

getItem(key: string) {
const value = this.localStorage.getItem(key);
try {
if (value === null) return value;
return JSON.parse(value);
} catch (error) {
return value;
}
}

setItem(key: string, value: string | object | boolean) {
if (typeof value === "string") {
return this.localStorage.setItem(key, value);
}
if (typeof value === "object" || typeof value === "boolean") {
return this.localStorage.setItem(key, JSON.stringify(value));
}
}

removeItem(key: string) {
return this.localStorage.removeItem(key);
}
}

return new LocalStore(localStorage);
}

+ 0
- 94
sandbox/lesspass-web-component/src/settings/SettingsPage.test.tsx View File

@@ -1,94 +0,0 @@
import React from "react";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import { InitApp } from "../App.test";
import { createMemoryHistory } from "history";
import { fakeLocalStore } from "../services/localStore";

test("test settings page when local storage is empty", async () => {
const history = createMemoryHistory();
render(
<InitApp
history={history}
store={fakeLocalStore({ masterPassword: "password" })}
/>
);
fireEvent.click(screen.queryByTestId("settings-link") as HTMLLinkElement);
expect(history.location.pathname).toBe("/settings");

const useMasterPasswordForAuth = screen.queryByTestId(
"use-master-password-for-auth-checkbox"
) as HTMLInputElement;

await waitFor(() => {
expect(useMasterPasswordForAuth).toBeInTheDocument();
expect(useMasterPasswordForAuth.type).toBe("checkbox");
expect(useMasterPasswordForAuth.checked).toBe(true);
});

const frRadioButton = screen.queryByTestId(
"fr-radio-button"
) as HTMLInputElement;
const enRadioButton = screen.queryByTestId(
"en-radio-button"
) as HTMLInputElement;
await waitFor(() => {
expect(enRadioButton).toBeInTheDocument();
expect(enRadioButton.type).toBe("radio");
expect(enRadioButton.checked).toBe(true);
expect(frRadioButton).toBeInTheDocument();
expect(frRadioButton.type).toBe("radio");
expect(frRadioButton.checked).toBe(false);
});
});

test("test settings page when local storage has saved info", async () => {
const history = createMemoryHistory();
render(
<InitApp
history={history}
store={fakeLocalStore({
masterPassword: "password",
settings: { useMasterPasswordForAuth: false, language: "fr" },
})}
/>
);
fireEvent.click(screen.queryByTestId("settings-link") as HTMLLinkElement);
expect(history.location.pathname).toBe("/settings");

const useMasterPasswordForAuth = screen.queryByTestId(
"use-master-password-for-auth-checkbox"
) as HTMLInputElement;

await waitFor(() => {
expect(useMasterPasswordForAuth).toBeInTheDocument();
expect(useMasterPasswordForAuth.type).toBe("checkbox");
expect(useMasterPasswordForAuth.checked).toBe(false);
});

const frRadioButton = screen.queryByTestId(
"fr-radio-button"
) as HTMLInputElement;
const enRadioButton = screen.queryByTestId(
"en-radio-button"
) as HTMLInputElement;
await waitFor(() => {
expect(enRadioButton.checked).toBe(false);
expect(frRadioButton.checked).toBe(true);
});
});

test("click on option save them in local store", async () => {
const store = fakeLocalStore({ masterPassword: "password" });
render(<InitApp store={store} />);
fireEvent.click(screen.queryByTestId("settings-link") as HTMLLinkElement);
const frRadioButton = screen.queryByTestId(
"fr-radio-button"
) as HTMLInputElement;
fireEvent.click(frRadioButton);
await waitFor(() => {
expect(store.getItem("settings")).toMatchObject({
useMasterPasswordForAuth: true,
language: "fr",
});
});
});

+ 0
- 71
sandbox/lesspass-web-component/src/settings/SettingsPage.tsx View File

@@ -1,71 +0,0 @@
import React from "react";
import { useTranslation } from "react-i18next";

type SettingsPageProps = {
settings: Settings;
setSettings: (settings: Settings) => void;
};

const SettingsPage = ({ settings, setSettings }: SettingsPageProps) => {
const { t, i18n } = useTranslation();
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
};
const setLanguage = (event: React.ChangeEvent<HTMLInputElement>) => {
const language = event.target.value;
changeLanguage(language);
setSettings({
...settings,
language,
});
};
return (
<div>
<div>
<input
type="checkbox"
id="use-master-password-for-auth-checkbox"
data-testid="use-master-password-for-auth-checkbox"
name="useMasterPasswordForAuth"
checked={settings.useMasterPasswordForAuth}
onChange={(event) =>
setSettings({
...settings,
useMasterPasswordForAuth: event.target.checked,
})
}
/>
<label htmlFor="use-master-password-for-auth-checkbox">
{t("settings.usemymasterpassword")}
</label>
<small>{t("settings.usemymasterpassworddescription")}</small>
</div>

<div>
<input
type="radio"
id="en-radio-button"
data-testid="en-radio-button"
name="language"
value="en"
checked={i18n.language === "en"}
onChange={setLanguage}
/>
<label htmlFor="en-radio-button">en</label>
<input
type="radio"
id="fr-radio-button"
data-testid="fr-radio-button"
name="language"
value="fr"
checked={i18n.language === "fr"}
onChange={setLanguage}
/>
<label htmlFor="fr-radio-button">fr</label>
<div>{t("settings.selectlanguage")}</div>
</div>
</div>
);
};

export default SettingsPage;

+ 0
- 7
sandbox/lesspass-web-component/src/settings/defaultSettings.ts View File

@@ -1,7 +0,0 @@
const defaultSettings: Settings = {
saveMasterPassword: true,
useMasterPasswordForAuth: true,
language: null,
};

export default defaultSettings;

+ 0
- 5
sandbox/lesspass-web-component/src/setupTests.ts View File

@@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

+ 0
- 15
sandbox/lesspass-web-component/src/store.ts View File

@@ -1,15 +0,0 @@
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import auth from "./auth/authSlice";
import passwords from "./passwords/passwordsSlice";

const rootReducer = combineReducers({ auth, passwords });

const store = configureStore({
reducer: rootReducer,
});

export type RootState = ReturnType<typeof store.getState>

export type AppDispatch = typeof store.dispatch;

export default store;

+ 0
- 8
sandbox/lesspass-web-component/src/styles/tailwind.css View File

@@ -1,8 +0,0 @@
/* purgecss start ignore */
@import "tailwindcss/base";
/* @import "./base.css"; */
@import "tailwindcss/components";
/* @import "./components.css"; */
/* purgecss end ignore */

@import "tailwindcss/utilities";

+ 0
- 104
sandbox/lesspass-web-component/src/unlock/MasterPassword.test.tsx View File

@@ -1,104 +0,0 @@
import React from "react";
import {
render,
screen,
waitFor,
fireEvent,
act,
} from "@testing-library/react";
import MasterPassword from "./MasterPassword";

const createFingerprintPromise = Promise.resolve([
{
color: "#FFB5DA",
icon: "fa-flask",
},
{
color: "#009191",
icon: "fa-archive",
},
{
color: "#B5DAFE",
icon: "fa-beer",
},
] as Fingerprint);

beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});

test("MasterPassword", async () => {
render(
<MasterPassword
value="password"
createFingerprint={jest.fn(() => createFingerprintPromise)}
onChange={jest.fn()}
/>
);
jest.advanceTimersByTime(500);
const masterPasswordInput = screen.queryByTestId(
"master-password-input"
) as HTMLInputElement;
expect(masterPasswordInput).toBeInTheDocument();
expect(masterPasswordInput.type).toBe("password");
expect(masterPasswordInput.value).toBe("password");
expect(masterPasswordInput).toBe(document.activeElement);
await waitFor(() => {
expect(screen.queryByTitle("icon-fa-flask")).toBeInTheDocument();
expect(screen.queryByTitle("icon-fa-archive")).toBeInTheDocument();
expect(screen.queryByTitle("icon-fa-beer")).toBeInTheDocument();
});
});

test("MasterPassword createFingerprint has been called after debounce", async () => {
const createFingerprint = jest.fn(() => createFingerprintPromise);
render(
<MasterPassword
value="password"
createFingerprint={createFingerprint}
onChange={jest.fn()}
/>
);
expect(createFingerprint).toHaveBeenCalledTimes(1);
expect(createFingerprint).not.toHaveBeenCalledWith("password");
jest.advanceTimersByTime(500);
expect(createFingerprint).toHaveBeenCalledWith("password");
await waitFor(() => {
expect(screen.queryByTestId("fingerprint")).toBeInTheDocument();
expect(screen.queryByTitle("icon-fa-flask")).toBeInTheDocument();
expect(screen.queryByTitle("icon-fa-archive")).toBeInTheDocument();
expect(screen.queryByTitle("icon-fa-beer")).toBeInTheDocument();
});
});

test("Remove fingerprint if MasterPassword is cleared", async () => {
render(
<MasterPassword
value=""
createFingerprint={jest.fn(() => createFingerprintPromise)}
onChange={jest.fn()}
/>
);
const masterPasswordInput = screen.queryByTestId(
"master-password-input"
) as HTMLInputElement;
fireEvent.change(masterPasswordInput, {
target: { value: "p" },
});
await waitFor(() => {
expect(screen.queryByTestId("fingerprint")).toBeInTheDocument();
});
fireEvent.change(masterPasswordInput, {
target: { value: "" },
});
act(() => {
jest.runAllTimers();
});
await waitFor(() => {
expect(screen.queryByTestId("fingerprint")).toBeNull();
});
});

+ 0
- 72
sandbox/lesspass-web-component/src/unlock/MasterPassword.tsx View File

@@ -1,72 +0,0 @@
import React, { useEffect, useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { getIcon } from "../icons";

type MasterPasswordProps = {
value: string;
createFingerprint: (masterPassword: MasterPassword) => Promise<Fingerprint>;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
};

const MasterPassword = ({
value,
createFingerprint,
onChange,
}: MasterPasswordProps) => {
const [fingerprint, setFingerprint] = useState<Fingerprint | null>(null);
const masterPasswordInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
masterPasswordInputRef.current?.focus();
}, []);
useEffect(() => {
let isSubscribed = true;
const fakeValue = Math.random().toString(36).substring(7);
createFingerprint(fakeValue).then((fakedFingerprint: Fingerprint) => {
if (isSubscribed) {
setFingerprint(fakedFingerprint);
}
});
const setFingerprintTimer = setTimeout(() => {
if (value) {
createFingerprint(value).then(setFingerprint);
} else {
setFingerprint(null);
}
}, 500);
return () => {
isSubscribed = false;
clearTimeout(setFingerprintTimer);
};
}, [createFingerprint, value]);
return (
<div>
<input
id="master-password-input"
data-testid="master-password-input"
name="masterPassword"
type="password"
ref={masterPasswordInputRef}
value={value}
onChange={onChange}
/>
{fingerprint && (
<div id="fingerprint" data-testid="fingerprint">
<FontAwesomeIcon
title={`icon-${fingerprint[0].icon}`}
icon={getIcon(fingerprint[0].icon)}
/>
<FontAwesomeIcon
title={`icon-${fingerprint[1].icon}`}
icon={getIcon(fingerprint[1].icon)}
/>
<FontAwesomeIcon
title={`icon-${fingerprint[2].icon}`}
icon={getIcon(fingerprint[2].icon)}
/>
</div>
)}
</div>
);
};

export default MasterPassword;

+ 0
- 51
sandbox/lesspass-web-component/src/unlock/UnlockPage.tsx View File

@@ -1,51 +0,0 @@
import React, { useEffect, useRef, useState } from "react";
import MasterPassword from "./MasterPassword";
import { createFingerprint } from "lesspass";

export const UnlockPage = ({
settings,
unlock,
}: {
settings: Settings;
unlock: (masterPassword: MasterPassword, saveMasterPassword:boolean) => void;
}) => {
const [masterPassword, setMasterPassword] = useState<MasterPassword>("");
const [saveMasterPassword, setSaveMasterPassword] = useState(settings.saveMasterPassword);
const masterPasswordInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
masterPasswordInputRef.current?.focus();
}, []);
return (
<form
onSubmit={(event) => {
event.preventDefault();
unlock(masterPassword, saveMasterPassword);
}}
>
<MasterPassword
value={masterPassword}
createFingerprint={createFingerprint}
onChange={(event) => {
event.persist();
setMasterPassword(event.target.value);
}}
/>
<input
id="save-master-password-checkbox"
data-testid="save-master-password-checkbox"
name="saveMasterPassword"
type="checkbox"
checked={saveMasterPassword}
onChange={(event) => {
setSaveMasterPassword(event.target.checked);
}}
/>
keep master password locally <a href="#">understand the risk</a>
<button id="unlock" data-testid="unlock">
unlock
</button>
</form>
);
};

export default UnlockPage;

+ 0
- 1
sandbox/lesspass-web-component/src/vendor.d.ts View File

@@ -1 +0,0 @@
declare module "lesspass";

+ 0
- 12
sandbox/lesspass-web-component/tailwind.config.js View File

@@ -1,12 +0,0 @@
module.exports = {
future: {
removeDeprecatedGapUtilities: true,
purgeLayersByDefault: true,
},
purge: false,
theme: {
extend: {},
},
variants: {},
plugins: [],
};

+ 0
- 25
sandbox/lesspass-web-component/tsconfig.json View File

@@ -1,25 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": [
"src"
]
}

+ 0
- 9533
sandbox/lesspass-web-component/yarn.lock
File diff suppressed because it is too large
View File


+ 0
- 69
sandbox/lesspass-website/.gitignore View File

@@ -1,69 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# dotenv environment variable files
.env*

# gatsby files
.cache/
public

# Mac files
.DS_Store

# Yarn
yarn-error.log
.pnp/
.pnp.js
# Yarn Integrity file
.yarn-integrity

+ 0
- 14
sandbox/lesspass-website/Dockerfile View File

@@ -1,14 +0,0 @@
FROM node:14-alpine AS builder
LABEL maintainer="LessPass <contact@lesspass.com>"
LABEL name="LessPass Frontend"
WORKDIR /opt/frontend
COPY package.json ./
RUN yarn install
COPY . /opt/frontend
RUN yarn build:static
RUN yarn build
FROM nginx:alpine
COPY --from=builder /opt/frontend/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

+ 0
- 1
sandbox/lesspass-website/gatsby-browser.js View File

@@ -1 +0,0 @@
import "./src/styles/global.css";

+ 0
- 33
sandbox/lesspass-website/gatsby-config.js View File

@@ -1,33 +0,0 @@
module.exports = {
siteMetadata: {
title: `LessPass`,
description: `stateless password manager`,
author: `Guillaume Vincent`,
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#3398eb`,
theme_color: `#3398eb`,
display: `minimal-ui`,
icon: `src/images/icon.png`,
},
},
`gatsby-plugin-postcss`,
`@wardpeet/gatsby-plugin-static-site`
],
}

+ 0
- 46
sandbox/lesspass-website/nginx.conf View File

@@ -1,46 +0,0 @@
server {
listen 80;
server_name frontend;
server_tokens off;

#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri /index.html;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

+ 0
- 46
sandbox/lesspass-website/package.json View File

@@ -1,46 +0,0 @@
{
"name": "lesspass-website",
"description": "LessPass web site",
"version": "0.1.0",
"license": "GPL-3.0",
"author": "Guillaume Vincent <guillaume@oslab.fr>",
"dependencies": {
"@wardpeet/gatsby-plugin-static-site": "^0.3.0",
"gatsby": "^4.6.1",
"gatsby-image": "^3.11.0",
"gatsby-plugin-manifest": "^4.6.0",
"gatsby-plugin-offline": "^5.6.0",
"gatsby-plugin-postcss": "^5.6.0",
"gatsby-plugin-react-helmet": "^5.6.0",
"gatsby-plugin-sharp": "^4.6.0",
"gatsby-source-filesystem": "^4.6.0",
"gatsby-transformer-sharp": "^4.6.0",
"lesspass-pure": "^9.5.6",
"prettier": "2.5.1",
"prop-types": "^15.8.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"tailwindcss": "^3.0.18"
},
"devDependencies": {
"@types/react-helmet": "^6.1.5"
},
"keywords": [
"lesspass"
],
"scripts": {
"no-analytics": "gatsby telemetry --disable",
"build:static": "cp -r node_modules/lesspass-pure/dist/* static/",
"build": "yarn no-analytics && gatsby build",
"develop": "gatsby develop",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
"start": "yarn no-analytics && yarn develop",
"serve": "gatsby serve",
"clean": "gatsby clean",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
},
"engines": {
"node": ">=14.15.0"
}
}

+ 0
- 3
sandbox/lesspass-website/postcss.config.js View File

@@ -1,3 +0,0 @@
module.exports = () => ({
plugins: [require("tailwindcss")],
});

+ 0
- 1
sandbox/lesspass-website/src/declaration.d.ts View File

@@ -1 +0,0 @@
declare module "*.png";

BIN
sandbox/lesspass-website/src/images/HowItWorks.png View File

Before After
Width: 1236  |  Height: 370  |  Size: 36 KiB

BIN
sandbox/lesspass-website/src/images/cli-badge.png View File

Before After
Width: 512  |  Height: 512  |  Size: 15 KiB

BIN
sandbox/lesspass-website/src/images/download-on-the-App-Store.png View File

Before After
Width: 323  |  Height: 125  |  Size: 4.2 KiB

+ 0
- 46
sandbox/lesspass-website/src/images/download-on-the-App-Store.svg View File

@@ -1,46 +0,0 @@
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
<title>Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917</title>
<g>
<g>
<g>
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
</g>
<g id="_Group_" data-name="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
<path id="_Path_2" data-name="&lt;Path&gt;" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
</g>
</g>
<g>
<path d="M42.30227,27.13965h-4.7334l-1.13672,3.35645H34.42727l4.4834-12.418h2.083l4.4834,12.418H43.438ZM38.0591,25.59082h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
<path d="M55.15969,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H48.4302v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.645,21.34766,55.15969,23.16406,55.15969,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30227,29.01563,53.24953,27.81934,53.24953,25.96973Z" style="fill: #fff"/>
<path d="M65.12453,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H58.395v1.50586h.03418A3.21162,3.21162,0,0,1,61.312,21.34766C63.60988,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26711,29.01563,63.21438,27.81934,63.21438,25.96973Z" style="fill: #fff"/>
<path d="M71.71047,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z" style="fill: #fff"/>
<path d="M86.065,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72609,30.6084,86.065,28.82617,86.065,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40039,1.16211-2.40039,3.10742c0,1.96191.89453,3.10645,2.40039,3.10645S92.76027,27.93164,92.76027,25.96973Z" style="fill: #fff"/>
<path d="M96.18606,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
<path d="M109.3843,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10207,25.13477Z" style="fill: #fff"/>
</g>
</g>
</g>
<g id="_Group_4" data-name="&lt;Group&gt;">
<g>
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z" style="fill: #fff"/>
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z" style="fill: #fff"/>
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z" style="fill: #fff"/>
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
</g>
</g>
</g>
</svg>

BIN
sandbox/lesspass-website/src/images/get-amo-badge.png View File

Before After
Width: 172  |  Height: 60  |  Size: 4.7 KiB

BIN
sandbox/lesspass-website/src/images/get-chrome-badge.png View File

Before After
Width: 496  |  Height: 150  |  Size: 6.1 KiB

BIN
sandbox/lesspass-website/src/images/get-it-on-fdroid.png View File

Before After
Width: 646  |  Height: 250  |  Size: 14 KiB

BIN
sandbox/lesspass-website/src/images/google-play-badge.png View File

Before After
Width: 646  |  Height: 250  |  Size: 9.6 KiB

BIN
sandbox/lesspass-website/src/images/icon.png View File

Before After
Width: 1024  |  Height: 1024  |  Size: 16 KiB

+ 0
- 6
sandbox/lesspass-website/src/pages/404.tsx View File

@@ -1,6 +0,0 @@
import React from "react"
import { PageProps } from "gatsby"

export default function NotFoundPage(props: PageProps) {
return <div>404</div>
}

+ 0
- 308
sandbox/lesspass-website/src/pages/index.tsx View File

@@ -1,308 +0,0 @@
import React, { useEffect } from "react";
import { withPrefix } from "gatsby";
import Helmet from "react-helmet";
import GooglePlayBadge from "../images/google-play-badge.png";
import ApplePlayBadge from "../images/download-on-the-App-Store.png";
import FDroidBadge from "../images/get-it-on-fdroid.png";
import ChromeBadge from "../images/get-chrome-badge.png";
import AMOBadge from "../images/get-amo-badge.png";
import CLIBadge from "../images/cli-badge.png";
import HowItWorks from "../images/HowItWorks.png";

export default function IndexPage() {
const currentYear = new Date().getFullYear();

useEffect(() => {
const script = document.createElement("script");
script.src = withPrefix("lesspass.min.js");
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, []);

return (
<>
<div className="relative bg-gray-800 overflow-hidden">
<Helmet>
<link href="lesspass.min.css" rel="stylesheet" />
</Helmet>
<div className="relative pt-6 pb-16 sm:pb-24">
<main className="mt-16 sm:mt-24">
<div className="mx-auto max-w-7xl">
<div className="lg:grid lg:grid-cols-12 lg:gap-8">
<div className="px-4 sm:px-6 sm:text-center md:max-w-2xl md:mx-auto lg:col-span-6 lg:text-left lg:flex lg:items-center">
<div>
<h1 className="mt-4 text-4xl tracking-tight font-extrabold text-white sm:mt-5 sm:leading-none lg:mt-6 lg:text-5xl xl:text-6xl">
<span className="md:block">LessPass</span>{" "}
<span className="md:block">
stateless password manager
</span>
</h1>
<p className="mt-3 text-base text-gray-300 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">
Stop wasting your time synchronizing your encrypted vault.
Remember one master password to access your passwords,
anywhere, anytime, from any device. No sync needed.
</p>
</div>
</div>
<div className="mt-16 sm:mt-24 lg:mt-0 lg:col-span-6">
<div className="bg-white sm:max-w-md sm:w-full sm:mx-auto sm:rounded-lg sm:overflow-hidden">
<div
style={{ minHeight: "449px" }}
className="lesspass--unbordered lesspass--full-width"
>
<div id="lesspass"></div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<div className="py-16 bg-white overflow-hidden lg:py-24">
<div className="relative max-w-xl mx-auto px-4 sm:px-6 lg:px-8 lg:max-w-7xl">
<div className="relative mt-12 lg:mt-24 lg:grid lg:grid-cols-2 lg:gap-8 lg:items-center">
<div className="relative">
<h3 className="text-2xl font-extrabold text-gray-900 tracking-tight sm:text-3xl">
Compute your password offline
</h3>
<p className="mt-3 text-lg text-gray-500">
LessPass computes a unique password using a site, login, and a
master password. You don't need to sync a password vault across
every device or to the cloud, because LessPass works offline!
</p>
</div>

<div className="mt-10 -mx-4 relative lg:mt-0" aria-hidden="true">
<svg
className="absolute left-1/2 transform -translate-x-1/2 translate-y-16 lg:hidden"
width={784}
height={404}
fill="none"
viewBox="0 0 784 404"
>
<defs>
<pattern
id="ca9667ae-9f92-4be7-abcb-9e3d727f2941"
x={0}
y={0}
width={20}
height={20}
patternUnits="userSpaceOnUse"
>
<rect
x={0}
y={0}
width={4}
height={4}
className="text-gray-200"
fill="currentColor"
/>
</pattern>
</defs>
<rect
width={784}
height={404}
fill="url(#ca9667ae-9f92-4be7-abcb-9e3d727f2941)"
/>
</svg>
<img
className="relative mx-auto"
width={490}
src={HowItWorks}
alt="How it works"
/>
</div>
</div>
</div>
</div>
<div className="bg-white">
<div className="max-w-7xl mx-auto py-12 px-4 sm:py-16 sm:px-6 lg:px-8 lg:py-20">
<div className="max-w-4xl mx-auto text-center">
<h2 className="text-3xl font-extrabold text-gray-900 sm:text-4xl">
Open Source
</h2>
<p className="mt-3 text-xl text-gray-500 sm:mt-4">
To us, a password manager is only as secure as it is transparent.
That's why LessPass is fully open source. Don't just take our word
for it, take a peek under the hood and see for yourself!
</p>
</div>
<dl className="mt-10 text-center sm:max-w-3xl sm:mx-auto sm:grid sm:grid-cols-3 sm:gap-8">
<div className="flex flex-col">
<dt className="order-2 mt-2 text-lg leading-6 font-medium text-gray-500">
Commits
</dt>
<dd className="order-1 text-5xl font-extrabold text-gray-900">
2200+
</dd>
</div>
<div className="flex flex-col mt-10 sm:mt-0">
<dt className="order-2 mt-2 text-lg leading-6 font-medium text-gray-500">
Contributors
</dt>
<dd className="order-1 text-5xl font-extrabold text-gray-900">
50+
</dd>
</div>
<div className="flex flex-col mt-10 sm:mt-0">
<dt className="order-2 mt-2 text-lg leading-6 font-medium text-gray-500">
Stars
</dt>
<dd className="order-1 text-5xl font-extrabold text-gray-900">
5k
</dd>
</div>
</dl>
</div>
<div className="bg-white">
<div className="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
<h2 className="text-center text-3xl font-extrabold text-gray-900 sm:text-4xl">
Use it everywhere
</h2>

<div className="mt-6 grid grid-cols-2 gap-0.5 md:grid-cols-3 lg:mt-8">
<div className="col-span-1 flex justify-center py-8 px-8">
<a href="https://play.google.com/store/apps/details?id=com.lesspass.android&hl=en">
<img
className="max-h-20"
src={GooglePlayBadge}
alt="Google Play badge"
/>
</a>
</div>
<div className="col-span-1 flex justify-center py-8 px-8">
<a href="https://apps.apple.com/app/id1531215924">
<img
className="max-h-20"
src={ApplePlayBadge}
alt="Apple Play badge"
/>
</a>
</div>
<div className="col-span-1 flex justify-center py-8 px-8">
<a href="https://f-droid.org/en/packages/com.lesspass.android/">
<img
className="max-h-20"
src={FDroidBadge}
alt="FDroid badge"
/>
</a>
</div>
<div className="col-span-1 flex justify-center py-8 px-8">
<a href="https://chrome.google.com/webstore/detail/lesspass/lcmbpoclaodbgkbjafnkbbinogcbnjih">
<img
className="max-h-20"
src={ChromeBadge}
alt="Chrome badge"
/>
</a>
</div>
<div className="col-span-1 flex justify-center py-8 px-8">
<a href="https://addons.mozilla.org/en-US/firefox/addon/lesspass/">
<img className="max-h-16" src={AMOBadge} alt="AMO badge" />
</a>
</div>
<div className="col-span-1 flex justify-center py-8 px-8">
<a href="https://github.com/lesspass/lesspass#cli">
<img className="max-h-16" src={CLIBadge} alt="Cli badge" />
</a>
</div>
</div>
</div>
</div>
</div>
<div className="bg-white pt-16 pb-20 px-4 sm:px-6 lg:pt-24 lg:pb-28 lg:px-8">
<div className="relative max-w-lg mx-auto divide-y-2 divide-gray-200 lg:max-w-7xl">
<div>
<h2 className="text-3xl tracking-tight font-extrabold text-gray-900 sm:text-4xl">
Recent publications
</h2>
<p className="mt-3 text-xl text-gray-500 sm:mt-4">
Read the latest blog posts from LessPass
</p>
</div>
<div className="mt-12 grid gap-16 pt-12 lg:grid-cols-3 lg:gap-x-5 lg:gap-y-12">
<div>
<div>
<span className="bg-indigo-100 text-indigo-800 inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium">
article
</span>
</div>
<a
href="https://blog.lesspass.com/2016-11-10/why-lesspass-change-license"
className="block mt-4"
>
<p className="text-xl font-semibold text-gray-900">
Why LessPass changed its license?
</p>
<p className="mt-3 text-base text-gray-500">
An open community starts with a good license. That’s why we
decided to change LessPass license from MIT to GPLv3.
</p>
</a>
</div>
<div>
<div>
<span className="bg-indigo-100 text-indigo-800 inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium">
article
</span>
</div>
<a
href="https://blog.lesspass.com/2016-10-19/how-does-it-work"
className="block mt-4"
>
<p className="text-xl font-semibold text-gray-900">
LessPass How Does It Work?
</p>
<p className="mt-3 text-base text-gray-500">
Managing your Internet passwords is not easy. You probably use
a password manager to help you. The system is simple, the tool
generates random passwords whenever you need them and saves
them into a file protected with a strong password. This system
is very robust, you only need to remember one password to rule
them all! Now you have a unique password for each site on the
Internet.
</p>
</a>
</div>
</div>
</div>
</div>
<footer className="bg-white">
<div className="max-w-7xl mx-auto py-12 px-4 overflow-hidden sm:px-6 lg:px-8">
<div className="mt-8 flex justify-center space-x-6">
<a
href="https://twitter.com/guillaume20100"
className="text-gray-400 hover:text-gray-500"
>
<span className="sr-only">Twitter</span>
<svg fill="currentColor" viewBox="0 0 24 24" className="h-6 w-6">
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
</svg>
</a>
<a
href="https://github.com/lesspass/lesspass/"
className="text-gray-400 hover:text-gray-500"
>
<span className="sr-only">Github</span>
<svg fill="currentColor" viewBox="0 0 24 24" className="h-6 w-6">
<path
fillRule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clipRule="evenodd"
/>
</svg>
</a>
</div>
<p className="mt-8 text-center text-base text-gray-400">
&copy; 2015-{currentYear} LessPass, Inc. All rights reserved.
</p>
</div>
</footer>
</>
);
}

+ 0
- 3
sandbox/lesspass-website/src/styles/global.css View File

@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

BIN
sandbox/lesspass-website/static/favicon.ico View File

Before After

BIN
sandbox/lesspass-website/static/fonts/fontawesome-webfont.674f50d2.eot View File


BIN
sandbox/lesspass-website/static/fonts/fontawesome-webfont.af7ae505.woff2 View File


BIN
sandbox/lesspass-website/static/fonts/fontawesome-webfont.b06871f2.ttf View File


BIN
sandbox/lesspass-website/static/fonts/fontawesome-webfont.fee66e71.woff View File


+ 0
- 2671
sandbox/lesspass-website/static/img/fontawesome-webfont.912ec66d.svg
File diff suppressed because it is too large
View File


+ 0
- 12
sandbox/lesspass-website/static/lesspass.min.css
File diff suppressed because it is too large
View File


+ 0
- 22
sandbox/lesspass-website/static/lesspass.min.js
File diff suppressed because it is too large
View File


+ 0
- 1
sandbox/lesspass-website/static/lesspass.min.js.map
File diff suppressed because it is too large
View File


+ 0
- 7
sandbox/lesspass-website/tailwind.config.js View File

@@ -1,7 +0,0 @@
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

+ 0
- 69
sandbox/lesspass-website/tsconfig.json View File

@@ -1,69 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */

/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */

/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */

/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */

/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */

/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

+ 0
- 10921
sandbox/lesspass-website/yarn.lock
File diff suppressed because it is too large
View File


+ 0
- 98
sandbox/test_import.py View File

@@ -1,98 +0,0 @@
from secrets import token_hex
from lesspass import password


def generate_random_entropy():
return int(token_hex(32), 16)


CHARACTER_SUBSETS = {
"lowercase": "abcdefghijklmnopqrstuvwxyz",
"uppercase": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"digits": "0123456789",
"symbols": "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
}


def reverse_entropy(entropy, remainder, modulo):
return entropy - modulo - remainder


def remove_at(i, s):
return s[:i] + s[i + 1 :]


def get_rule_len(rule):
if rule == "lowercase":
return 26
if rule == "digits":
return 10


rules = [
"lowercase",
"digits",
]
old_password = "bar4"
chars_remaining = "%s" % old_password
one_char_per_rule = []
for rule in reversed(rules):
for i, c in enumerate(old_password):
print(f"char {c} index {i}")
rule_pool = CHARACTER_SUBSETS[rule]
if c in rule_pool:
print(f"{c} in {rule_pool}")
char_per_rule = (c, rule, i)
print(char_per_rule)
one_char_per_rule.append(char_per_rule)
chars_remaining = remove_at(i, chars_remaining)
break
else:
print(f"{c} not in {rule_pool}. skipping")
pass
print("-------")


print("chars to insert:", one_char_per_rule)

entropy = 654762009171870546215294809657
j = 7
print(f"e{j}:", entropy)
j -= 1
for i, rule in enumerate(one_char_per_rule):
print(f"e * ({len(old_password) - i - 1}) + {rule[2]}")
entropy = entropy * (len(old_password) - i - 1) + rule[2]
print(f"e{j}:", entropy)
j -= 1

for i, rule in enumerate(one_char_per_rule):
_pool_of_char = CHARACTER_SUBSETS[rule[1]]
print("len _pool_of_char:", len(_pool_of_char))
position = _pool_of_char.find(rule[0])
print("position:", position)
entropy = entropy * len(_pool_of_char) + position
print(f"e{j}:", entropy)
j -= 1

pool_of_char = CHARACTER_SUBSETS["lowercase"] + CHARACTER_SUBSETS["digits"]
# print(chars_remaining)
while chars_remaining != "":
last_char = chars_remaining[-1]
chars_remaining = chars_remaining[:-1]
entropy = entropy * len(pool_of_char) + pool_of_char.find(last_char)
print(f"e{j}:", entropy)
j -= 1
print(entropy)
print("x" * 100)
print(
password._render_password(
entropy,
{
"lowercase": True,
"uppercase": False,
"digits": True,
"symbols": False,
"length": 5,
},
)
)

Loading…
Cancel
Save