@@ -0,0 +1,71 @@ | |||
# 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 variables file | |||
.env | |||
# gatsby files | |||
.cache/ | |||
public | |||
# Mac files | |||
.DS_Store | |||
# Yarn | |||
yarn-error.log | |||
.pnp/ | |||
.pnp.js | |||
# Yarn Integrity file | |||
.yarn-integrity | |||
.netlify/ |
@@ -0,0 +1,9 @@ | |||
- name: Guillaume Vincent | |||
bio: | | |||
Software craftsman. Humanist, open source addict, blogger and fan of scuba diving. | |||
I ♥ code, design, UX and cats. Creator of LessPass | |||
avatar: ./avatars/guillaume-vincent.jpg | |||
social: | |||
- url: https://twitter.com/guillaume20100 | |||
- url: https://github.com/guillaumevincent | |||
featured: true |
@@ -0,0 +1,123 @@ | |||
--- | |||
title: LessPass How Does It Work? | |||
author: Guillaume Vincent | |||
date: 2016-10-19 | |||
hero: ./images/HowItWorks.png | |||
--- | |||
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. | |||
I have used this system for a long time. But every time I met the same problems: | |||
* How do I synchronize this file on all my devices ? | |||
* How do I access a password on my parents’ computer without installing my password manager ? | |||
* How do I access a password on my phone, without any installed app ? | |||
So I searched for a simpler solution and as none satisfied all those criteria I created LessPass. | |||
* I want a password manager with open source code, that does not require synchronization. | |||
The trick is to compute passwords rather than generate and store random passwords. | |||
LessPass generates unique passwords for websites, email accounts, or anything else based on a master password and information you know. | |||
LessPass is different from other password managers that you can find on the Internet because: | |||
* It does not save your passwords in a database ; | |||
* It does not need to sync your devices ; | |||
* It is open source (source code can be audited). | |||
The system uses a pure function, i.e. a function that given the same parameters will always give the same result. In our case, given a login, a master password, a site and options it will return a unique password. | |||
No need to save your passwords in an encrypted file. You just need to access the tool to recalculate a password from information that you know (mostly the login). | |||
To raise the cost of breaking your master password, the generation of the password must be time consuming, especially by brute force. So LessPass uses PBKDF2 with 100,000 iterations and a hash function sha-256. | |||
Password generation is based on pure functions. | |||
The hash generated by the first function is derived and processed in order to respect the requested options (i.e. length, lowercase, uppercase, numbers, special characters): | |||
![how it works demo](./images/HowItWorks.png) | |||
The source code is available [here](https://github.com/lesspass/lesspass/), I invite you to have a look . | |||
## What does it look like? | |||
A picture is worth a thousand words: | |||
![lesspass demo](./images/demo.gif) | |||
The simplest way to try it is to use the official website https://lesspass.com/ to type in your site, login and master password. The password will be generated on the fly so you just have to copy it (using the button or the keyboard). | |||
Try it on your phone, on another computer, even offline, it will give the same result. No need to sync. | |||
## Is it available on my OS/device? | |||
Yes, as soon as you have access to a browser it’s available to you. But we went beyond that and added: | |||
* an Android application ; | |||
* a Chrome extension ; | |||
* a Firefox extension ; | |||
* a command line interface ; | |||
* and the official site (for more security use browser extensions). | |||
## What about complex password rules? | |||
Sometimes sites have specific password rules. For instance, some banks only accept passwords made of numbers. So you have to remember both a strong password and complex rules. | |||
Well, we built a “connected” version to tackle that. It works by saving your password’s profile, i.e. everything **except the master password and the generated password** to be able to generate the password. Then, next time you need this password you just have to select the profile and type the master password. | |||
Here is what a profile looks like: | |||
{ | |||
"login": "38491092", | |||
"site": "www.ingdirect.fr", | |||
"lowercase": false, | |||
"uppercase": false, | |||
"symbols": false, | |||
"numbers": true, | |||
"counter": 1, | |||
"length": 6 | |||
} | |||
Below is a user connecting to it’s account to use his bank’s account profile: | |||
The connected version can help you save complex profiles. | |||
![lesspass demo](./images/demo-lesspass-connected.gif) | |||
## Self Hosted | |||
You can host your own LessPass Database if you do not want to use the official one. The requirement for self-hosting is to have `docker` and `docker-compose` installed on your machine. | |||
Look at the documentation on github if you are interested in | |||
## How do I change a password without changing my master password? | |||
That’s the purpose of the counter field in the options field set, increment it and you will get a new password. | |||
Counter field | |||
## How to contribute? | |||
* If you are a scientist, help us write a white paper ; | |||
* Send pull requests to improve or fix the source code ; | |||
* Rate the Firefox or the Chrome extension ; | |||
* Send us to the stars on github ; | |||
## Open Culture | |||
LessPass is open source (GPLv3 license), we refuse to install cookies, analysis tools on our applications (there are no Google Analytics, or links to external services on our tools). | |||
We host our code on Vultr’s servers and our DNS are managed by Gandi . | |||
We really like the idea of an open culture: all bugs that we find are visible. | |||
We document our algorithms and our approach: no magic, no black box. | |||
We love feedback and your ideas to improve the tool: we are aware of some limitations (change of master password, for example) but we are working to improve the product. | |||
We are not sponsored by any company, developing LessPass is done during our free time. | |||
If you have comments or questions, feel free to email us at contact@lesspass.com | |||
I want to thank Édouard Lopez for all the work on user experience and great feadback on the product! |
@@ -0,0 +1,54 @@ | |||
module.exports = { | |||
siteMetadata: { | |||
title: `LessPass Blog`, | |||
name: `LessPass`, | |||
siteUrl: `https://blog.lesspass.com`, | |||
description: `LessPass blog`, | |||
hero: { | |||
heading: `LessPass blog`, | |||
maxWidth: 652, | |||
}, | |||
social: [ | |||
{ | |||
name: `twitter`, | |||
url: `https://twitter.com/guillaume20100`, | |||
}, | |||
{ | |||
name: `github`, | |||
url: `https://github.com/lesspass/lesspass`, | |||
}, | |||
], | |||
}, | |||
plugins: [ | |||
{ | |||
resolve: "@narative/gatsby-theme-novela", | |||
options: { | |||
contentPosts: "content/posts", | |||
contentAuthors: "content/authors", | |||
basePath: "/", | |||
authorsPage: true, | |||
sources: { | |||
local: true, | |||
// contentful: true, | |||
}, | |||
}, | |||
}, | |||
{ | |||
resolve: `gatsby-plugin-manifest`, | |||
options: { | |||
name: `LessPass Blog`, | |||
short_name: `LessPass`, | |||
start_url: `/`, | |||
background_color: `#fff`, | |||
theme_color: `#fff`, | |||
display: `standalone`, | |||
icon: `src/assets/favicon.png`, | |||
}, | |||
}, | |||
{ | |||
resolve: `gatsby-plugin-netlify-cms`, | |||
options: { | |||
}, | |||
}, | |||
], | |||
}; |
@@ -0,0 +1,22 @@ | |||
{ | |||
"private": true, | |||
"name": "site", | |||
"version": "1.0.0", | |||
"license": "MIT", | |||
"scripts": { | |||
"build": "gatsby build", | |||
"dev": "gatsby develop", | |||
"clean": "gatsby clean", | |||
"proxy": "netlify-cms-proxy-server" | |||
}, | |||
"dependencies": { | |||
"@narative/gatsby-theme-novela": "^0.*", | |||
"gatsby": "^2.13.41", | |||
"gatsby-plugin-manifest": "^2.2.4", | |||
"gatsby-plugin-netlify-cms": "^4.1.40", | |||
"netlify-cms-app": "^2.11.23", | |||
"netlify-cms-proxy-server": "^1.1.4", | |||
"react": "^16.8.6", | |||
"react-dom": "^16.8.6" | |||
} | |||
} |
@@ -0,0 +1,131 @@ | |||
import React from "react"; | |||
/** | |||
* Paste in your SVG logo and return it from this component. | |||
* Make sure you have a height set for your logo. | |||
* It is recommended to keep the height within 25-35px. | |||
* Logo comes with a property vallue called `fill`. `fill` is useful | |||
* when you want to change your logo depending on the theme you are on. | |||
*/ | |||
export default function Logo({ fill }) { | |||
return ( | |||
<svg width="50" height="50" version="1.1"> | |||
<defs> | |||
<filter colorInterpolationFilters="sRGB"> | |||
<feFlood | |||
floodColor="#000" | |||
floodOpacity="0.4" | |||
result="flood" | |||
></feFlood> | |||
<feComposite | |||
in="flood" | |||
in2="SourceGraphic" | |||
operator="in" | |||
result="composite1" | |||
></feComposite> | |||
<feGaussianBlur result="blur" stdDeviation="2"></feGaussianBlur> | |||
<feOffset dx="4" dy="4" result="offset"></feOffset> | |||
<feComposite | |||
in="SourceGraphic" | |||
in2="offset" | |||
operator="over" | |||
result="composite2" | |||
></feComposite> | |||
</filter> | |||
<filter id="a" colorInterpolationFilters="sRGB"> | |||
<feFlood | |||
floodColor="#000" | |||
floodOpacity="0.4" | |||
result="flood" | |||
></feFlood> | |||
<feComposite | |||
in="flood" | |||
in2="SourceGraphic" | |||
operator="in" | |||
result="composite1" | |||
></feComposite> | |||
<feGaussianBlur result="blur" stdDeviation="1"></feGaussianBlur> | |||
<feOffset dx="4" dy="4" result="offset"></feOffset> | |||
<feComposite | |||
in="SourceGraphic" | |||
in2="offset" | |||
operator="over" | |||
result="composite2" | |||
></feComposite> | |||
</filter> | |||
<filter colorInterpolationFilters="sRGB"> | |||
<feFlood | |||
floodColor="#000" | |||
floodOpacity="0.4" | |||
result="flood" | |||
></feFlood> | |||
<feComposite | |||
in="flood" | |||
in2="SourceGraphic" | |||
operator="in" | |||
result="composite1" | |||
></feComposite> | |||
<feGaussianBlur result="blur" stdDeviation="2"></feGaussianBlur> | |||
<feOffset dx="4" dy="4" result="offset"></feOffset> | |||
<feComposite | |||
in="SourceGraphic" | |||
in2="offset" | |||
operator="over" | |||
result="composite2" | |||
></feComposite> | |||
</filter> | |||
</defs> | |||
<g fillOpacity="1" stroke="none" transform="translate(-5.421 -5)"> | |||
<rect | |||
width="40.035" | |||
height="40.035" | |||
x="-19.72" | |||
y="22.706" | |||
fill="#0275d8" | |||
opacity="0.9" | |||
rx="8.007" | |||
transform="rotate(-45)" | |||
></rect> | |||
<path | |||
style={{ | |||
lineHeight: "normal", | |||
InkscapeFontSpecification: "Sans", | |||
WebkitTextIndent: "0", | |||
textIndent: "0", | |||
WebkitTextAlign: "start", | |||
textAlign: "start", | |||
WebkitTextDecorationLine: "none", | |||
textDecorationLine: "none", | |||
WebkitTextTransform: "none", | |||
textTransform: "none", | |||
marker: "none", | |||
}} | |||
fill="#fff" | |||
fillRule="nonzero" | |||
strokeWidth="10" | |||
d="M79.92 55.04c-12.363 0-22.471 10.136-22.471 22.5 0 10.28 6.991 18.98 16.453 21.628v45.872h18.253c.126 0 .225-.099.225-.225v-10.237c0-.126-.104-.26-.225-.225H85.94V130.5h6.215c.126 0 .225-.1.225-.225v-10.238a.223.223 0 00-.225-.225H85.94V99.168c9.462-2.648 16.453-11.348 16.453-21.628 0-12.364-10.108-22.5-22.472-22.5zm0 8.831c7.597 0 13.641 6.073 13.641 13.67 0 7.596-6.044 13.64-13.64 13.64-7.596 0-13.64-6.044-13.64-13.64 0-7.597 6.044-13.67 13.64-13.67z" | |||
baselineShift="baseline" | |||
color="#000" | |||
direction="ltr" | |||
display="inline" | |||
enableBackground="accumulate" | |||
filter="url(#a)" | |||
fontFamily="Sans" | |||
fontSize="medium" | |||
fontStretch="normal" | |||
fontStyle="normal" | |||
fontVariant="normal" | |||
fontWeight="normal" | |||
letterSpacing="normal" | |||
overflow="visible" | |||
textAnchor="start" | |||
textDecoration="none" | |||
transform="matrix(.33333 0 0 .33333 3.78 -3.347)" | |||
visibility="visible" | |||
wordSpacing="normal" | |||
writingMode="lr-tb" | |||
></path> | |||
</g> | |||
</svg> | |||
); | |||
} |
@@ -0,0 +1,21 @@ | |||
import React from "react"; | |||
import Layout from "@narative/gatsby-theme-novela/src/components/Layout"; | |||
import Section from "@narative/gatsby-theme-novela/src/components/Section"; | |||
import SEO from "@narative/gatsby-theme-novela/src/components/SEO"; | |||
import Headings from "@narative/gatsby-theme-novela/src/components/Headings"; | |||
function NotFoundPage() { | |||
return ( | |||
<Layout> | |||
<SEO /> | |||
<Section> | |||
<div style={{ marginTop: "100px" }}> | |||
<Headings.h1>404: Page Not Found</Headings.h1> | |||
</div> | |||
</Section> | |||
</Layout> | |||
); | |||
} | |||
export default NotFoundPage; |
@@ -0,0 +1,43 @@ | |||
backend: # Set up your backend | |||
name: gitlab | |||
local_backend: true # Optional if you want to use the local proxy mode | |||
publish_mode: editorial_workflow | |||
media_folder: media # I'm not using these default media directories at the moment. | |||
public_folder: /media | |||
collections: | |||
- name: "posts" | |||
label: "Posts" | |||
folder: "content/posts" | |||
create: true | |||
delete: true | |||
path: "{{slug}}/index" | |||
slug: "{{year}}-{{month}}-{{day}}-{{slug}}" | |||
media_folder: "images" | |||
public_folder: "./images" | |||
fields: | |||
- { label: "Title", name: "title", widget: "string" } | |||
- { label: "Author", name: "author", widget: "relation", collection: "authors", valueField: "name", searchFields: ["name"] } | |||
- { label: "Publish Date", name: "date", widget: "datetime", format: "YYYY-MM-DD", dateFormat: "YYYY-MM-DD", timeFormat: false } | |||
- { label: "Excerpt", name: "excerpt", widget: "string", required: false } | |||
- { label: "Hero", name: "hero", widget: "image" } | |||
- { label: "Body", name: "body", widget: "markdown" } | |||
- name: "authors" | |||
label: "Authors" | |||
folder: "content/authors/authors" | |||
create: true | |||
delete: true | |||
format: "yml" | |||
identifier_field: "name" | |||
media_folder: "avatars" | |||
public_folder: "./avatars" | |||
editor: | |||
preview: false | |||
fields: | |||
- { label: Name, name: name, widget: string } | |||
- { label: Bio, name: bio, widget: string } | |||
- { label: Featured, name: featured, widget: boolean, default: false } | |||
- { label: Avatar, name: avatar, widget: image } | |||
- { label: Social, name: social, widget: list, collapsed: false, field: { label: URL, name: url, widget: string } } |