浏览代码

[mono repo] remove git cached files

pull/342/head
Guillaume Vincent 6 年前
父节点
当前提交
1e6cec0734
共有 100 个文件被更改,包括 26896 次插入5 次删除
  1. +0
    -1
      backend
  2. +17
    -0
      backend/.editorconfig
  3. +94
    -0
      backend/.gitignore
  4. +13
    -0
      backend/.travis.yml
  5. +19
    -0
      backend/Dockerfile
  6. +24
    -0
      backend/README.md
  7. +0
    -0
      backend/api/__init__.py
  8. +84
    -0
      backend/api/admin.py
  9. +5
    -0
      backend/api/apps.py
  10. +73
    -0
      backend/api/migrations/0001_initial.py
  11. +38
    -0
      backend/api/migrations/0002_password.py
  12. +37
    -0
      backend/api/migrations/0003_mv_entries_to_password.py
  13. +29
    -0
      backend/api/migrations/0004_remove_entries_password_info_models.py
  14. +20
    -0
      backend/api/migrations/0005_password_version.py
  15. +25
    -0
      backend/api/migrations/0006_change_default_password_profile.py
  16. +0
    -0
      backend/api/migrations/__init__.py
  17. +90
    -0
      backend/api/models.py
  18. +6
    -0
      backend/api/permissions.py
  19. +14
    -0
      backend/api/serializers.py
  20. +0
    -0
      backend/api/tests/__init__.py
  21. +25
    -0
      backend/api/tests/factories.py
  22. +104
    -0
      backend/api/tests/tests_passwords.py
  23. +15
    -0
      backend/api/urls.py
  24. +14
    -0
      backend/api/views.py
  25. +8
    -0
      backend/entrypoint.sh
  26. +0
    -0
      backend/lesspass/__init__.py
  27. +193
    -0
      backend/lesspass/settings.py
  28. +7
    -0
      backend/lesspass/urls.py
  29. +7
    -0
      backend/lesspass/wsgi.py
  30. +10
    -0
      backend/manage.py
  31. +10
    -0
      backend/requirements.txt
  32. +14
    -0
      backend/supervisord.conf
  33. +0
    -1
      cli
  34. +17
    -0
      cli/.editorconfig
  35. +1
    -0
      cli/.gitignore
  36. +13
    -0
      cli/.travis.yml
  37. +80
    -0
      cli/README.md
  38. +145
    -0
      cli/cli.js
  39. +4619
    -0
      cli/package-lock.json
  40. +37
    -0
      cli/package.json
  41. +292
    -0
      cli/test.js
  42. +0
    -1
      cordova
  43. +17
    -0
      cordova/.editorconfig
  44. +4
    -0
      cordova/.gitignore
  45. +28
    -0
      cordova/README.md
  46. +6
    -0
      cordova/build.sh
  47. +28
    -0
      cordova/config.xml
  48. +16
    -0
      cordova/gulpfile.js
  49. +6373
    -0
      cordova/package-lock.json
  50. +31
    -0
      cordova/package.json
  51. 二进制
      cordova/www/dist/674f50d287a8c48dc19ba404d20fe713.eot
  52. +2671
    -0
      cordova/www/dist/912ec66d7572ff821749319396470bde.svg
  53. 二进制
      cordova/www/dist/af7ae505a9eed503f8b8e6982036873e.woff2
  54. 二进制
      cordova/www/dist/b06871f281fee6b241d60582ae9369b9.ttf
  55. 二进制
      cordova/www/dist/favicon.ico
  56. 二进制
      cordova/www/dist/fee66e712a8a08eef5805a46892932ad.woff
  57. +47
    -0
      cordova/www/dist/i18n/de.json
  58. +47
    -0
      cordova/www/dist/i18n/en.json
  59. +47
    -0
      cordova/www/dist/i18n/es.json
  60. +47
    -0
      cordova/www/dist/i18n/fr.json
  61. +47
    -0
      cordova/www/dist/i18n/zh-CN.json
  62. +47
    -0
      cordova/www/dist/i18n/zh.json
  63. +6
    -0
      cordova/www/dist/lesspass.min.css
  64. +38
    -0
      cordova/www/dist/lesspass.min.js
  65. 二进制
      cordova/www/icons/mipmap-hdpi/icon.png
  66. 二进制
      cordova/www/icons/mipmap-ldpi/icon.png
  67. 二进制
      cordova/www/icons/mipmap-mdpi/icon.png
  68. 二进制
      cordova/www/icons/mipmap-xhdpi/icon.png
  69. 二进制
      cordova/www/img/icon.png
  70. 二进制
      cordova/www/img/screen.png
  71. +20
    -0
      cordova/www/index.html
  72. +46
    -0
      cordova/www/js/index.js
  73. +0
    -1
      core
  74. +17
    -0
      core/.editorconfig
  75. +2
    -0
      core/.gitignore
  76. +17
    -0
      core/.travis.yml
  77. +89
    -0
      core/README.md
  78. +3873
    -0
      core/dist/lesspass.js
  79. +1
    -0
      core/dist/lesspass.min.js
  80. +49
    -0
      core/example/index.html
  81. +3673
    -0
      core/package-lock.json
  82. +57
    -0
      core/package.json
  83. +31
    -0
      core/src/hmac.browser.js
  84. +8
    -0
      core/src/hmac.js
  85. +69
    -0
      core/src/lesspass.js
  86. +42
    -0
      core/src/pbkdf2.browser.js
  87. +16
    -0
      core/src/pbkdf2.js
  88. +98
    -0
      core/test/api.tests.js
  89. +109
    -0
      core/test/entropy.tests.js
  90. +21
    -0
      core/test/hmac.tests.js
  91. +19
    -0
      core/test/karma.conf.js
  92. +227
    -0
      core/test/pbkdf2.tests.js
  93. +0
    -1
      cozy
  94. +17
    -0
      cozy/.editorconfig
  95. +1
    -0
      cozy/.gitignore
  96. +24
    -0
      cozy/README.md
  97. 二进制
      cozy/dist/674f50d287a8c48dc19ba404d20fe713.eot
  98. +2671
    -0
      cozy/dist/912ec66d7572ff821749319396470bde.svg
  99. 二进制
      cozy/dist/af7ae505a9eed503f8b8e6982036873e.woff2
  100. 二进制
      cozy/dist/b06871f281fee6b241d60582ae9369b9.ttf

+ 0
- 1
backend

@@ -1 +0,0 @@
Subproject commit 670e4b39c06416a1f3946a7d97074dc6f42751b7

+ 17
- 0
backend/.editorconfig 查看文件

@@ -0,0 +1,17 @@
# editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.py]
indent_size = 4

+ 94
- 0
backend/.gitignore 查看文件

@@ -0,0 +1,94 @@
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# IPython Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
venv/
ENV/

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject

/db.sqlite3
www/

+ 13
- 0
backend/.travis.yml 查看文件

@@ -0,0 +1,13 @@
dist: trusty
sudo: required
language: python
python:
- 3.5
addons:
postgresql: "9.5"
services:
- postgresql
env:
- DATABASE_ENGINE=django.db.backends.postgresql_psycopg2 DATABASE_NAME=postgres DATABASE_USER=postgres DATABASE_PASSWORD= DATABASE_HOST=localhost DATABASE_PORT=5432
install: pip install -r requirements.txt
script: python manage.py test

+ 19
- 0
backend/Dockerfile 查看文件

@@ -0,0 +1,19 @@
FROM python:3.5-alpine

RUN apk add --no-cache supervisor netcat-openbsd postgresql-dev gcc python3-dev musl-dev

RUN mkdir /backend
WORKDIR /backend
COPY requirements.txt /backend/
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

COPY api/ /backend/api/
COPY lesspass/ /backend/lesspass/
COPY manage.py /backend/manage.py

COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

+ 24
- 0
backend/README.md 查看文件

@@ -0,0 +1,24 @@
# LessPass backend

REST API used by [lesspass-pure](https://github.com/lesspass/pure) to store password profiles

* python 3
* django rest framework
* django rest framework jwt
* djoser
* postgresql
* gunicorn

## Tests

pip install -r requirements.txt
python manage.py test

## License

This project is licensed under the terms of the GNU GPLv3.


## Issues

report issues on [LessPass project](https://github.com/lesspass/lesspass/issues)

+ 0
- 0
backend/api/__init__.py 查看文件


+ 84
- 0
backend/api/admin.py 查看文件

@@ -0,0 +1,84 @@
from api import models
from api.models import LessPassUser

from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.db.models import Count


class UserCreationForm(forms.ModelForm):
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

class Meta:
model = LessPassUser
fields = ('email',)

def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords don't match")
return password2

def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user


class UserChangeForm(forms.ModelForm):
password = ReadOnlyPasswordHashField()

class Meta:
model = LessPassUser
fields = ('email', 'password', 'is_active', 'is_admin')

def clean_password(self):
return self.initial["password"]


class LessPassUserAdmin(BaseUserAdmin):
form = UserChangeForm
add_form = UserCreationForm

list_display = ('email', 'is_admin', 'column_passwords_count')
list_filter = ('is_admin', 'is_active')
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Permissions', {'fields': ('is_admin',)}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2')}
),
)
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ()

def get_queryset(self, request):
return models.LessPassUser.objects.annotate(passwords_count=Count('passwords'))

def column_passwords_count(self, instance):
return instance.passwords_count

column_passwords_count.short_description = 'Password count'
column_passwords_count.admin_order_field = 'passwords_count'


class PasswordAdmin(admin.ModelAdmin):
list_display = ('id', 'user',)
search_fields = ('user__email',)
ordering = ('user',)


admin.site.register(models.Password, PasswordAdmin)
admin.site.register(LessPassUser, LessPassUserAdmin)
admin.site.unregister(Group)

+ 5
- 0
backend/api/apps.py 查看文件

@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
name = 'api'

+ 73
- 0
backend/api/migrations/0001_initial.py 查看文件

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-04-06 10:11
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='LessPassUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('email', models.EmailField(max_length=255, unique=True, verbose_name='email address')),
('is_active', models.BooleanField(default=True)),
('is_admin', models.BooleanField(default=False)),
],
options={
'verbose_name_plural': 'Users',
},
),
migrations.CreateModel(
name='Entry',
fields=[
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('login', models.CharField(default='', max_length=255)),
('site', models.CharField(default='', max_length=255)),
('title', models.CharField(blank=True, max_length=255, null=True)),
('username', models.CharField(blank=True, max_length=255, null=True)),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('description', models.TextField(blank=True, null=True)),
('url', models.URLField(blank=True, null=True)),
],
options={
'verbose_name_plural': 'Entries',
},
),
migrations.CreateModel(
name='PasswordInfo',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('counter', models.IntegerField(default=1)),
('settings', models.TextField()),
('length', models.IntegerField(default=12)),
],
options={
'verbose_name_plural': 'Password info',
},
),
migrations.AddField(
model_name='entry',
name='password',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.PasswordInfo'),
),
migrations.AddField(
model_name='entry',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='entries', to=settings.AUTH_USER_MODEL),
),
]

+ 38
- 0
backend/api/migrations/0002_password.py 查看文件

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-09-24 08:11
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('api', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='Password',
fields=[
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('login', models.CharField(blank=True, max_length=255, null=True)),
('site', models.CharField(blank=True, max_length=255, null=True)),
('lowercase', models.BooleanField(default=True)),
('uppercase', models.BooleanField(default=True)),
('symbols', models.BooleanField(default=True)),
('numbers', models.BooleanField(default=True)),
('counter', models.IntegerField(default=1)),
('length', models.IntegerField(default=12)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='passwords', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

+ 37
- 0
backend/api/migrations/0003_mv_entries_to_password.py 查看文件

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-09-24 08:13
from __future__ import unicode_literals

from django.db import migrations

import json

from api import models


def create_password_with(entry):
settings = json.dumps(entry.password.settings)
lowercase = 'lowercase' in settings
uppercase = 'uppercase' in settings
symbols = 'symbols' in settings
numbers = 'numbers' in settings
user = models.LessPassUser.objects.get(id=entry.user.id)
models.Password.objects.create(id=entry.id, site=entry.site, login=entry.login, user=user,
lowercase=lowercase, uppercase=uppercase, symbols=symbols, numbers=numbers,
counter=entry.password.counter, length=entry.password.length)


def mv_entries_to_password(apps, schema_editor):
Entry = apps.get_model("api", "Entry")
for entry in Entry.objects.all():
create_password_with(entry)


class Migration(migrations.Migration):
dependencies = [
('api', '0002_password'),
]

operations = [
migrations.RunPython(mv_entries_to_password),
]

+ 29
- 0
backend/api/migrations/0004_remove_entries_password_info_models.py 查看文件

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-10-25 06:33
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('api', '0003_mv_entries_to_password'),
]

operations = [
migrations.RemoveField(
model_name='entry',
name='password',
),
migrations.RemoveField(
model_name='entry',
name='user',
),
migrations.DeleteModel(
name='Entry',
),
migrations.DeleteModel(
name='PasswordInfo',
),
]

+ 20
- 0
backend/api/migrations/0005_password_version.py 查看文件

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.3 on 2016-11-21 13:30
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0004_remove_entries_password_info_models'),
]

operations = [
migrations.AddField(
model_name='password',
name='version',
field=models.IntegerField(default=1),
),
]

+ 25
- 0
backend/api/migrations/0006_change_default_password_profile.py 查看文件

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-19 18:03
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0005_password_version'),
]

operations = [
migrations.AlterField(
model_name='password',
name='length',
field=models.IntegerField(default=16),
),
migrations.AlterField(
model_name='password',
name='version',
field=models.IntegerField(default=2),
),
]

+ 0
- 0
backend/api/migrations/__init__.py 查看文件


+ 90
- 0
backend/api/models.py 查看文件

@@ -0,0 +1,90 @@
import uuid
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)


class LesspassUserManager(BaseUserManager):
def create_user(self, email, password=None):
if not email:
raise ValueError('Users must have an email address')

user = self.model(
email=self.normalize_email(email),
)

user.set_password(password)
user.save(using=self._db)
return user

def create_superuser(self, email, password):
user = self.create_user(email, password=password, )
user.is_admin = True
user.save(using=self._db)
return user


class LessPassUser(AbstractBaseUser):
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)

objects = LesspassUserManager()

USERNAME_FIELD = 'email'

def get_full_name(self):
return self.email

def get_short_name(self):
return self.email

def __str__(self):
return self.email

def has_perm(self, perm, obj=None):
return True

def has_module_perms(self, app_label):
return True

@property
def is_superuser(self):
return self.is_admin

@property
def is_staff(self):
return self.is_admin

class Meta:
verbose_name_plural = "Users"


class DateMixin(models.Model):
created = models.DateTimeField(auto_now_add=True, verbose_name='created')
modified = models.DateTimeField(auto_now=True, verbose_name='modified')

class Meta:
abstract = True


class Password(DateMixin):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(LessPassUser, on_delete=models.CASCADE, related_name='passwords')
login = models.CharField(max_length=255, null=True, blank=True)
site = models.CharField(max_length=255, null=True, blank=True)

lowercase = models.BooleanField(default=True)
uppercase = models.BooleanField(default=True)
symbols = models.BooleanField(default=True)
numbers = models.BooleanField(default=True)

length = models.IntegerField(default=16)
counter = models.IntegerField(default=1)

version = models.IntegerField(default=2)

def __str__(self):
return str(self.id)


+ 6
- 0
backend/api/permissions.py 查看文件

@@ -0,0 +1,6 @@
from rest_framework import permissions


class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.user == request.user

+ 14
- 0
backend/api/serializers.py 查看文件

@@ -0,0 +1,14 @@
from api import models
from rest_framework import serializers


class PasswordSerializer(serializers.ModelSerializer):
class Meta:
model = models.Password
fields = ('id', 'login', 'site', 'lowercase', 'uppercase', 'symbols', 'numbers', 'counter', 'length',
'version', 'created', 'modified')
read_only_fields = ('created', 'modified')

def create(self, validated_data):
user = self.context['request'].user
return models.Password.objects.create(user=user, **validated_data)

+ 0
- 0
backend/api/tests/__init__.py 查看文件


+ 25
- 0
backend/api/tests/factories.py 查看文件

@@ -0,0 +1,25 @@
import factory

from api import models


class UserFactory(factory.DjangoModelFactory):
class Meta:
model = models.LessPassUser

email = factory.Sequence(lambda n: 'u{0}@lesspass.com'.format(n))
password = factory.PostGenerationMethodCall('set_password', 'password')
is_admin = False


class AdminFactory(UserFactory):
is_admin = True


class PasswordFactory(factory.DjangoModelFactory):
class Meta:
model = models.Password

user = factory.SubFactory(UserFactory)
login = 'admin@oslab.fr'
site = 'lesspass.com'

+ 104
- 0
backend/api/tests/tests_passwords.py 查看文件

@@ -0,0 +1,104 @@
from rest_framework.test import APITestCase, APIClient

from api import models
from api.tests import factories


class LogoutApiTestCase(APITestCase):
def test_get_passwords_401(self):
response = self.client.get('/api/passwords/')
self.assertEqual(401, response.status_code)


class LoginApiTestCase(APITestCase):
def setUp(self):
self.user = factories.UserFactory()
self.client = APIClient()
self.client.force_authenticate(user=self.user)

def test_get_empty_passwords(self):
request = self.client.get('/api/passwords/')
self.assertEqual(0, len(request.data['results']))

def test_retrieve_its_own_passwords(self):
password = factories.PasswordFactory(user=self.user)
request = self.client.get('/api/passwords/')
self.assertEqual(1, len(request.data['results']))
self.assertEqual(password.site, request.data['results'][0]['site'])

def test_cant_retrieve_other_passwords(self):
not_my_password = factories.PasswordFactory(user=factories.UserFactory())
request = self.client.get('/api/passwords/%s/' % not_my_password.id)
self.assertEqual(404, request.status_code)

def test_delete_its_own_passwords(self):
password = factories.PasswordFactory(user=self.user)
self.assertEqual(1, models.Password.objects.all().count())
request = self.client.delete('/api/passwords/%s/' % password.id)
self.assertEqual(204, request.status_code)
self.assertEqual(0, models.Password.objects.all().count())

def test_cant_delete_other_password(self):
not_my_password = factories.PasswordFactory(user=factories.UserFactory())
self.assertEqual(1, models.Password.objects.all().count())
request = self.client.delete('/api/passwords/%s/' % not_my_password.id)
self.assertEqual(404, request.status_code)
self.assertEqual(1, models.Password.objects.all().count())

def test_create_password(self):
password = {
"site": "lesspass.com",
"login": "test@oslab.fr",
"lowercase": True,
"uppercase": True,
"number": True,
"symbol": True,
"counter": 1,
"length": 12
}
self.assertEqual(0, models.Password.objects.count())
self.client.post('/api/passwords/', password)
self.assertEqual(1, models.Password.objects.count())

def test_create_password_v2(self):
password = {
"site": "lesspass.com",
"login": "test@oslab.fr",
"lowercase": True,
"uppercase": True,
"number": True,
"symbol": True,
"counter": 1,
"length": 12,
"version": 2
}
self.client.post('/api/passwords/', password)
self.assertEqual(2, models.Password.objects.first().version)

def test_update_password(self):
password = factories.PasswordFactory(user=self.user)
self.assertNotEqual('facebook.com', password.site)
new_password = {
"site": "facebook.com",
"login": "test@oslab.fr",
"lowercase": True,
"uppercase": True,
"number": True,
"symbol": True,
"counter": 1,
"length": 12
}
request = self.client.put('/api/passwords/%s/' % password.id, new_password)
self.assertEqual(200, request.status_code, request.content.decode('utf-8'))
password_updated = models.Password.objects.get(id=password.id)
self.assertEqual('facebook.com', password_updated.site)

def test_cant_update_other_password(self):
not_my_password = factories.PasswordFactory(user=factories.UserFactory())
self.assertEqual('lesspass.com', not_my_password.site)
new_password = {
"site": "facebook",
}
request = self.client.put('/api/passwords/%s/' % not_my_password.id, new_password)
self.assertEqual(404, request.status_code)
self.assertEqual(1, models.Password.objects.all().count())

+ 15
- 0
backend/api/urls.py 查看文件

@@ -0,0 +1,15 @@
import rest_framework_jwt.views
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter

from api import views

router = DefaultRouter()
router.register(r'passwords', views.PasswordViewSet, base_name='passwords')

urlpatterns = [
url(r'^', include(router.urls)),
url(r'^tokens/auth/', rest_framework_jwt.views.obtain_jwt_token),
url(r'^tokens/refresh/', rest_framework_jwt.views.refresh_jwt_token),
url(r'^auth/', include('djoser.urls')),
]

+ 14
- 0
backend/api/views.py 查看文件

@@ -0,0 +1,14 @@
from api import models, serializers
from api.permissions import IsOwner

from rest_framework import permissions, viewsets


class PasswordViewSet(viewsets.ModelViewSet):
serializer_class = serializers.PasswordSerializer
permission_classes = (permissions.IsAuthenticated, IsOwner,)
search_fields = ('site', 'email',)
ordering_fields = ('site', 'email', 'created')

def get_queryset(self):
return models.Password.objects.filter(user=self.request.user)

+ 8
- 0
backend/entrypoint.sh 查看文件

@@ -0,0 +1,8 @@
#!/bin/sh

while ! nc -z db 5432; do sleep 3; done

python manage.py migrate
python manage.py collectstatic --clear --no-input

exec "$@"

+ 0
- 0
backend/lesspass/__init__.py 查看文件


+ 193
- 0
backend/lesspass/settings.py 查看文件

@@ -0,0 +1,193 @@
import logging
import os
import random
import sys
import datetime
from envparse import env

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def get_secret_key(secret_key):
if not secret_key:
return "".join([random.choice("abcdefghijklmnopqrstuvwxyz0123456789!@#$^&*(-_=+)") for i in range(50)])
return secret_key


SECRET_KEY = env('SECRET_KEY', preprocessor=get_secret_key, default=None)

DEBUG = env.bool('DJANGO_DEBUG', default=False)

ALLOWED_HOSTS = env('ALLOWED_HOSTS', cast=list, default=['localhost', '127.0.0.1', '.lesspass.com'])

ADMINS = (('Guillaume Vincent', 'guillaume@oslab.fr'),)

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'djoser',
'api'
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

CORS_ORIGIN_ALLOW_ALL = True

ROOT_URLCONF = 'lesspass.urls'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

WSGI_APPLICATION = 'lesspass.wsgi.application'

DATABASES = {
'default': {
'ENGINE': env('DATABASE_ENGINE', default='django.db.backends.sqlite3'),
'NAME': env('DATABASE_NAME', default=os.path.join(BASE_DIR, 'db.sqlite3')),
'USER': env('DATABASE_USER', default=None),
'PASSWORD': env('DATABASE_PASSWORD', default=None),
'HOST': env('DATABASE_HOST', default=None),
'PORT': env('DATABASE_PORT', default=None),
}
}

AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', },
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', },
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', },
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', },
]

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

STATIC_URL = '/static/'

STATIC_ROOT = os.path.join(BASE_DIR, 'www', 'static')

MEDIA_URL = '/media/'

MEDIA_ROOT = os.path.join(BASE_DIR, 'www', 'media')

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
}
},
'root': {
'handlers': ['console'],
'level': env('DJANGO_LOG_LEVEL', default='DEBUG'),
}
}

AUTH_USER_MODEL = 'api.LessPassUser'

JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
'JWT_ALLOW_REFRESH': True,
}

REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 1000,
'DEFAULT_FILTER_BACKENDS': (
'rest_framework.filters.OrderingFilter',
'rest_framework.filters.SearchFilter',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
),
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}


class DisableMigrations(object):
def __contains__(self, item):
return True

def __getitem__(self, item):
return None


TESTS_IN_PROGRESS = False
if 'test' in sys.argv[1:] or 'jenkins' in sys.argv[1:]:
logging.disable(logging.CRITICAL)
PASSWORD_HASHERS = (
'django.contrib.auth.hashers.MD5PasswordHasher',
)
DEBUG = False
TEMPLATE_DEBUG = False
TESTS_IN_PROGRESS = True
MIGRATION_MODULES = DisableMigrations()

DJOSER = {
'PASSWORD_RESET_CONFIRM_URL': '#/password/reset/confirm/{uid}/{token}',
'ACTIVATION_URL': '#/activate/{uid}/{token}'
}

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

if DEBUG:
EMAIL_BACKEND = os.getenv('EMAIL_BACKEND', 'django.core.mail.backends.console.EmailBackend')
else:
EMAIL_BACKEND = os.getenv('EMAIL_BACKEND', 'django.core.mail.backends.smtp.EmailBackend')
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'contact@lesspass.com')
EMAIL_HOST = os.getenv('EMAIL_HOST', 'localhost')
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '')
EMAIL_PORT = env.int('EMAIL_PORT', default=25)
EMAIL_SUBJECT_PREFIX = os.getenv('EMAIL_SUBJECT_PREFIX', '[LessPass] ')
EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', default=False)
EMAIL_USE_SSL = env.bool('EMAIL_USE_SSL', default=False)

+ 7
- 0
backend/lesspass/urls.py 查看文件

@@ -0,0 +1,7 @@
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
url(r'^api/', include('api.urls')),
url(r'^admin/', admin.site.urls),
]

+ 7
- 0
backend/lesspass/wsgi.py 查看文件

@@ -0,0 +1,7 @@
import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lesspass.settings")

application = get_wsgi_application()

+ 10
- 0
backend/manage.py 查看文件

@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lesspass.settings")

from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)

+ 10
- 0
backend/requirements.txt 查看文件

@@ -0,0 +1,10 @@
Django==1.11
django-cors-middleware==1.3.1
djangorestframework==3.6.2
djangorestframework-jwt==1.10.0
psycopg2==2.7.1
gunicorn==19.7.1
djoser==0.5.4
envparse==0.2.0
# tests
factory-boy==2.8.1

+ 14
- 0
backend/supervisord.conf 查看文件

@@ -0,0 +1,14 @@
[supervisord]
nodaemon=true
logfile=/dev/null
pidfile=/var/run/supervisord.pid

[program:gunicorn]
directory=/backend
command=gunicorn lesspass.wsgi:application -w 2 -b :8000
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

+ 0
- 1
cli

@@ -1 +0,0 @@
Subproject commit d41a7867ffd23cb76c14a4505d72b1830b0e5e4e

+ 17
- 0
cli/.editorconfig 查看文件

@@ -0,0 +1,17 @@
# editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.py]
indent_size = 4

+ 1
- 0
cli/.gitignore 查看文件

@@ -0,0 +1 @@
node_modules

+ 13
- 0
cli/.travis.yml 查看文件

@@ -0,0 +1,13 @@
dist: trusty
sudo: required
language: node_js
node_js:
- '6'
addons:
apt:
packages:
- xsel
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3

+ 80
- 0
cli/README.md 查看文件

@@ -0,0 +1,80 @@
# LessPass cli

LessPass passwords directly in your terminal


## Install

```
$ npm install --global lesspass-cli
```


## Usage

```
$ lesspass --help

build LessPass passwords directly in command line

Usage
$ lesspass <site> <login> [masterPassword] [options]

Options
-l add lowercase in password
-u add uppercase in password
-d add digits in password
-s add symbols in password

--no-lowercase remove lowercase from password
--no-uppercase remove uppercase from password
--no-digits remove digits from password
--no-symbols remove symbols from password

--length, -L int (default 16)
--counter, -c int (default 1)
--clipboard, -C copy generated password to clipboard rather than displaying it.
Need pbcopy (OSX), xclip (Linux) or clip (Windows).

Examples
# no symbols
$ lesspass lesspass.com contact@lesspass.com password --no-symbols
OlfK63bmUhqrGODR
# no symbols shortcut
$ lesspass lesspass.com contact@lesspass.com password -lud
OlfK63bmUhqrGODR
# only digits and length of 8
$ lesspass lesspass.com contact@lesspass.com -d -L8
master password:
75837019
```


## FAQ

### How can I generate a password if I have a quote (`'`) in my master password ?

Escape the quote like this :

lesspass lesspass.com contact@lesspass.com 'my parents'\'' house is great'

Replace `'` by `'\''`

### password prompt

If you omit master password, lesspass-cli will ask you a master password:

lesspass lesspass.com contact@lesspass.com --length=14
master password:


## License

This project is licensed under the terms of the GNU GPLv3.

## Issues

report issues on [LessPass project](https://github.com/lesspass/lesspass/issues)

+ 145
- 0
cli/cli.js 查看文件

@@ -0,0 +1,145 @@
#!/usr/bin/env node
"use strict";
const clipboardy = require("clipboardy");
const meow = require("meow");
const LessPass = require("lesspass");
const read = require("read");
const chalk = require("chalk");

const helpMessage = `
Usage
$ lesspass <site> <login> [masterPassword] [options]

Options
-l add lowercase in password
-u add uppercase in password
-d add digits in password
-s add symbols in password

--no-lowercase remove lowercase from password
--no-uppercase remove uppercase from password
--no-digits remove digits from password
--no-symbols remove symbols from password

--length, -L int (default 16)
--counter, -c int (default 1)

--clipboard, -C copy generated password to clipboard rather than displaying it.
Need pbcopy (OSX), xsel (Linux) or clip (Windows).

Examples
# no symbols
$ lesspass lesspass.com contact@lesspass.com password --no-symbols
OlfK63bmUhqrGODR

# no symbols shortcut
$ lesspass lesspass.com contact@lesspass.com password -lud
OlfK63bmUhqrGODR

# only digits and length of 8
$ lesspass lesspass.com contact@lesspass.com -d -L8
master password:
75837019`;

const cli = meow(helpMessage, {
flags: {
site: { type: "string" },
login: { type: "string" },
length: {
type: "string",
alias: "L"
},
counter: {
type: "string",
alias: "c"
},
clipboard: {
type: "boolean",
alias: "C"
},
l: { type: "boolean" },
u: { type: "boolean" },
d: { type: "boolean" },
s: { type: "boolean" }
}
});

function calcPassword(site, login, masterPassword, passwordProfile) {
LessPass.generatePassword(site, login, masterPassword, passwordProfile).then(
function(generatedPassword) {
if (passwordProfile.clipboard) {
clipboardy
.write(generatedPassword)
.then(() => {
console.log("Copied to clipboard");
process.exit();
})
.catch(err => {
console.error(chalk.red("Copy failed."));
console.error(err.message);
process.exit(1);
});
} else {
console.log(generatedPassword);
process.exit();
}
}
);
}

function hasNoShortOption(options) {
return !["l", "u", "d", "s"].some(function(shortOption) {
return typeof options[shortOption] !== "undefined" && options[shortOption];
});
}

function getOptionBoolean(options, optionString) {
let shortOption = optionString.substring(0, 1);
if (options[shortOption]) {
return true;
}
if (typeof options[optionString] === "undefined") {
return hasNoShortOption(options);
}
return options[optionString];
}

const lowercase = getOptionBoolean(cli.flags, "lowercase");
const uppercase = getOptionBoolean(cli.flags, "uppercase");
const symbols = getOptionBoolean(cli.flags, "symbols");
const digits = getOptionBoolean(cli.flags, "digits");

const passwordProfile = {
lowercase: lowercase,
uppercase: uppercase,
symbols: symbols,
numbers: digits,
clipboard: cli.flags.clipboard || false,
length: cli.flags.length || 16,
counter: cli.flags.counter || 1
};

const site = cli.input[0];
let login = cli.input[1];

if (typeof login === "undefined") {
login = "";
}

if (typeof site === "undefined") {
console.log(chalk.red("site cannot be empty"));
console.log("type lesspass --help");
process.exit(-1);
}

if (cli.input.length === 3) {
const masterPassword = cli.input[2];
calcPassword(site, login, masterPassword, passwordProfile);
} else {
read({ prompt: "master password: ", silent: true }, function(er, password) {
if (er && er.message === "canceled") {
process.exit();
}
calcPassword(site, login, password, passwordProfile);
});
}

+ 4619
- 0
cli/package-lock.json
文件差异内容过多而无法显示
查看文件


+ 37
- 0
cli/package.json 查看文件

@@ -0,0 +1,37 @@
{
"name": "lesspass-cli",
"version": "5.1.1",
"description": "build LessPass passwords directly in command line",
"keywords": [
"cli",
"cli-app",
"lesspass",
"password"
],
"license": "GPL-3.0",
"author": "Guillaume Vincent <guillaume@oslab.fr>",
"files": [
"cli.js"
],
"bin": {
"lesspass": "cli.js"
},
"repository": "lesspass/cli",
"scripts": {
"precommit": "npm test",
"prepush": "npm test",
"test": "ava"
},
"dependencies": {
"chalk": "2.3.1",
"clipboardy": "1.2.3",
"lesspass": "6.0.0",
"meow": "4.0.0",
"read": "1.0.7"
},
"devDependencies": {
"ava": "^0.25.0",
"execa": "^0.9.0",
"husky": "^0.14.3"
}
}

+ 292
- 0
cli/test.js 查看文件

@@ -0,0 +1,292 @@
import test from "ava";
import execa from "execa";

test("default options", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password"
]);
t.is(stdout, "\\g-A1-.OHEwrXjT#");
});

test("no login", async t => {
return execa.shell('echo password | ./cli.js "lesspass.com"').then(result => {
t.is(result.stdout, "master password: 7Cw-APO5Co?G>W>u");
});
});

test("options can be before parameters", async t => {
const { stdout } = await execa("./cli.js", [
"-C",
"lesspass.com",
"contact@lesspass.com",
"password"
]);
t.is(stdout, "Copied to clipboard");
});

test("long options can be before parameters", async t => {
const { stdout } = await execa("./cli.js", [
"--clipboard",
"lesspass.com",
"contact@lesspass.com",
"password"
]);
t.is(stdout, "Copied to clipboard");
});

test("length", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"--length=14"
]);
t.is(stdout, "=0\\A-.OHEKvwrX");
});

test("length shortcut", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-L=14"
]);
t.is(stdout, "=0\\A-.OHEKvwrX");
});

test("counter", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"--counter=2"
]);
t.is(stdout, "Vf:F1'!I`8Y2`GBE");
});

test("counter shortcut", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-c=2"
]);
t.is(stdout, "Vf:F1'!I`8Y2`GBE");
});

test("no lowercase", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"--no-lowercase"
]);
t.is(stdout, 'JBG\\`3{+0["(E\\JJ');
});

test("no lowercase shortcut", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-uds"
]);
t.is(stdout, 'JBG\\`3{+0["(E\\JJ');
});

test("only lowercase", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-l"
]);
t.is(stdout, "fmnujoqgcxmpffyh");
});

test("no uppercase", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"--no-uppercase"
]);
t.is(stdout, 'jbg\\`3{+0["(e\\jj');
});

test("no uppercase shortcut", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-lds"
]);
t.is(stdout, 'jbg\\`3{+0["(e\\jj');
});

test("only uppercase", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-u"
]);
t.is(stdout, "FMNUJOQGCXMPFFYH");
});

test("no digits", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"--no-digits"
]);
t.is(stdout, ";zkB#m]mNF$;J_Ej");
});

test("no digits shortcut", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-lus"
]);
t.is(stdout, ";zkB#m]mNF$;J_Ej");
});

test("only digits", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-d"
]);
t.is(stdout, "7587019305478072");
});

test("no symbols", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"--no-symbols"
]);
t.is(stdout, "OlfK63bmUhqrGODR");
});

test("no symbols shortcut", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-lud"
]);
t.is(stdout, "OlfK63bmUhqrGODR");
});

test("only symbols", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-s"
]);
t.is(stdout, "<\"]|'`%};'`>-'[,");
});

test("test space in password", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"my Master Password"
]);
t.is(stdout, "D1PBB34\\#fh!LY={");
});

test("doc 1", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"--no-symbols"
]);
t.is(stdout, "OlfK63bmUhqrGODR");
});

test("doc 1 options before", async t => {
const { stdout } = await execa("./cli.js", [
"--no-symbols",
"lesspass.com",
"contact@lesspass.com",
"password"
]);
t.is(stdout, "OlfK63bmUhqrGODR");
});

test("doc 2", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-lud"
]);
t.is(stdout, "OlfK63bmUhqrGODR");
});

test("doc 2 options before", async t => {
const { stdout } = await execa("./cli.js", [
"-lud",
"lesspass.com",
"contact@lesspass.com",
"password"
]);
t.is(stdout, "OlfK63bmUhqrGODR");
});

test("doc 3", async t => {
const { stdout } = await execa("./cli.js", [
"lesspass.com",
"contact@lesspass.com",
"password",
"-d",
"-L8"
]);
t.is(stdout, "75837019");
});

test("doc 3 options before", async t => {
const { stdout } = await execa("./cli.js", [
"-d",
"-L8",
"lesspass.com",
"contact@lesspass.com",
"password"
]);
t.is(stdout, "75837019");
});

test("doc 3 options before and after", async t => {
const { stdout } = await execa("./cli.js", [
"-d",
"lesspass.com",
"contact@lesspass.com",
"password",
"-L8"
]);
t.is(stdout, "75837019");
});

test("nrt numbers should be considered as string not integers", async t => {
const p = execa("./cli.js", ["example.org", "123", "password"]);
const p2 = execa("./cli.js", ["example.org", "0123", "password"]);
const p3 = execa("./cli.js", ["example.org", '"0123"', "password"]);
const p4 = execa("./cli.js", ["example.org", "00123", "password"]);
return Promise.all([p, p2, p3, p4]).then(v => {
t.is(v[0].stdout, "sMb8}N&`J4wkF9q~");
t.is(v[1].stdout, "5,4SqhB2[=/h\\DZh");
t.is(v[2].stdout, "u0Fz)EOJ4i\\{{;a~");
t.is(v[3].stdout, '=}|O7hN0ZHdjQ{">');
});
});

+ 0
- 1
cordova

@@ -1 +0,0 @@
Subproject commit be8f56640289947a0ee7bac7f0047532e71c7f1a

+ 17
- 0
cordova/.editorconfig 查看文件

@@ -0,0 +1,17 @@
# editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.py]
indent_size = 4

+ 4
- 0
cordova/.gitignore 查看文件

@@ -0,0 +1,4 @@
platforms
plugins
res
node_modules

+ 28
- 0
cordova/README.md 查看文件

@@ -0,0 +1,28 @@
# LessPass cordova

LessPass Android application

## Requirements

* Cordova (`npm install -g cordova`)

## Check everything ok

cordova requirements

## Test

cordova run android
## Build Release

cordova build --release -- --keystore=~/Android/lesspass.keystore --storePassword=... --alias=lesspass --password=...
## License

This project is licensed under the terms of the GNU GPLv3.


## Issues

report issues on [LessPass project](https://github.com/lesspass/lesspass/issues)

+ 6
- 0
cordova/build.sh 查看文件

@@ -0,0 +1,6 @@
#!/usr/bin/env bash

cp www/icons/mipmap-hdpi/icon.png platforms/android/res/mipmap-hdpi/icon.png
cp www/icons/mipmap-ldpi/icon.png platforms/android/res/mipmap-ldpi/icon.png
cp www/icons/mipmap-mdpi/icon.png platforms/android/res/mipmap-mdpi/icon.png
cp www/icons/mipmap-xhdpi/icon.png platforms/android/res/mipmap-xhdpi/icon.png

+ 28
- 0
cordova/config.xml 查看文件

@@ -0,0 +1,28 @@
<?xml version='1.0' encoding='utf-8'?>
<widget android-versionCode="20101"
id="com.lesspass.android"
version="2.1.1"
xmlns="http://www.w3.org/ns/widgets"
xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>LessPass</name>
<description>
Stateless Password Manager
</description>
<author email="contact@lesspass.com" href="https://lesspass.com">
LessPass Team
</author>
<content src="index.html"/>
<access origin="*"/>
<allow-intent href="https://*/*"/>
<allow-navigation href="https://*/*"/>
<platform name="android">
<preference name="android-minSdkVersion" value="21"/>
<allow-intent href="market:*"/>
<icon density="hdpi" src="www/icons/mipmap-hdpi/icon.png"/>
<icon density="ldpi" src="www/icons/mipmap-ldpi/icon.png"/>
<icon density="mdpi" src="www/icons/mipmap-mdpi/icon.png"/>
<icon density="xhdpi" src="www/icons/mipmap-xhdpi/icon.png"/>
</platform>
<engine name="android" spec="^6.2.3"/>
<plugin name="cordova-plugin-whitelist" spec="~1.3.2"/>
</widget>

+ 16
- 0
cordova/gulpfile.js 查看文件

@@ -0,0 +1,16 @@
'use strict';

const gulp = require('gulp');

gulp.task('lesspass', [], function() {
return gulp.src(['node_modules/lesspass-pure/dist/**/*'])
.pipe(gulp.dest('www/dist/'));
});

gulp.task('build', [], function() {
gulp.start('lesspass');
});

gulp.task('default', ['build'], function() {

});

+ 6373
- 0
cordova/package-lock.json
文件差异内容过多而无法显示
查看文件


+ 31
- 0
cordova/package.json 查看文件

@@ -0,0 +1,31 @@
{
"name": "lesspass-cordova",
"version": "1.0.0",
"description": "LessPass cordova app",
"license": "GPL-3.0",
"author": "Guillaume Vincent <guillaume@oslab.fr>",
"repository": "lesspass/cordova",
"scripts": {
"prebuild": "npm prune && npm install",
"clean": "rm -rf plateforms/android && rm -rf www/dist/ && cordova clean",
"build": "npm run clean && gulp && ./build.sh"
},
"dependencies": {
"cordova": "^7.0.1",
"cordova-android": "^6.2.3",
"cordova-plugin-whitelist": "~1.3.2",
"lesspass-pure": "^4.6.2"
},
"devDependencies": {
"gulp": "^3.9.1"
},
"private": true,
"cordova": {
"plugins": {
"cordova-plugin-whitelist": {}
},
"platforms": [
"android"
]
}
}

二进制
cordova/www/dist/674f50d287a8c48dc19ba404d20fe713.eot 查看文件


+ 2671
- 0
cordova/www/dist/912ec66d7572ff821749319396470bde.svg
文件差异内容过多而无法显示
查看文件


二进制
cordova/www/dist/af7ae505a9eed503f8b8e6982036873e.woff2 查看文件


二进制
cordova/www/dist/b06871f281fee6b241d60582ae9369b9.ttf 查看文件


二进制
cordova/www/dist/favicon.ico 查看文件

之前 之后

二进制
cordova/www/dist/fee66e712a8a08eef5805a46892932ad.woff 查看文件


+ 47
- 0
cordova/www/dist/i18n/de.json 查看文件

@@ -0,0 +1,47 @@
{
"Advanced options": "Erweiterte Optionen",
"Copied": "Kopiert!",
"Counter": "Zähler",
"CounterFieldHelp": "Erhöhen Sie diesen Wert, um das zu erstellende Passwort zu ändern, ohne Ihr Masterpasswort zu ändern.",
"CreatePassword": "Möchten Sie eines erstellen?",
"DBNotRunning": "Ihre LessPass Datenbank läuft nicht",
"DefaultOptionLocalStorage": "Wir benutzen den Cache Ihres Browsers, um Ihre Einstellungen als Standard lokal zu speichern. Jedesmal, wenn Sie die App öffnen, werden diese Einstellungen geladen.",
"Email": "email",
"EmailAlreadyExist": "Diese email-Adresse ist bereits registriert. Möchten Sie sich einloggen oder Ihr Passwort wiederherstellen?",
"EmailInvalid": "Bitte geben Sie eine gültige email-Adresse ein",
"EmailRequiredError": "Wir benötigen eine email-Adresse, um Ihr Konto zu finden.",
"Encrypt my master password": "Verschlüssele mein Masterpasswort",
"ForgotPassword": "Passwort vergessen?",
"Generate": "Erstelle",
"Length": "Länge",
"LessPass Database Url": "LessPass Datenbank Url",
"Login": "Login",
"LoginFormInvalid": "LessPass URL, email-Adresse und Passwort sind obligatorisch",
"LoginIncorrectError": "Die email-Adresse und das Passwort, die Sie eingegeben haben, entsprechen nicht unseren Daten. Bitte überprüfen Sie sie und versuchen es nochmal.",
"Master Password": "Masterpasswort",
"Next": "Nächste",
"NoMatchFor": "Ups! Dafür gibt es keine Übereinstimmung",
"NoPassword": "Sie haben kein Passwort-Profil in Ihrer Datenbank gespeichert.",
"Password profile deleted": "Kennwort Profil gelöscht",
"PasswordProfileCopied": "Ihr Passwort-Profil wurde kopiert",
"PasswordResetRequired": "Ein Passwort ist erforderlich",
"PasswordResetSuccessful": "Ihr Passwort wurde erfolgreich zurückgesetzt.",
"Previous": "Vorherige",
"Register": "Registrieren",
"Reset my password": "Mein Passwort zurücksetzen",
"ResetLinkExpired": "Der Link zum Zurücksetzen des Passworts ist abgelaufen.",
"Save options": "Optionen speichern",
"Sign In": "Anmelden",
"Site": "Seite",
"SiteLoginMasterPasswordMandatory": "Die Felder für Seite, Anmeldename und Masterpasswort sind obligatorisch.",
"SorryCopy": "Es tut uns leid, dass die Kopie nur auf modernen Browsern funktioniert",
"UNDO": "STORNIEREN",
"UpdateYourSearch": "Bitte erweitern Sie Ihre Suche.",
"Version": "Version",
"WarningV1Deprecated": "Version 1 ist veraltet und wird bald gelöscht werden. Wir empfehlen Ihnen dringend, Ihre Passwörter auf die Version 2 zu migrieren.",
"WelcomeRegister": "Willkommen {email}, danke für die Anmeldung.",
"Your options have been saved successfully": "Ihre Optionen wurden erfolgreich gespeichert",
"resetPasswordSuccess": "Wenn die E-Mail-Adresse {email} mit einem LessPass-Konto verknüpft ist, erhalten Sie in Kürze eine E-Mail von LessPass mit Anweisungen zum Zurücksetzen Ihres Passworts.",
"version": "Version",
"versionShortcut": "V"
}

+ 47
- 0
cordova/www/dist/i18n/en.json 查看文件

@@ -0,0 +1,47 @@
{
"Advanced options": "Advanced options",
"Copied": "copied!",
"Counter": "Counter",
"CounterFieldHelp": "Increment this value to change the generated password without changing your master password.",
"CreatePassword": "Would you like to create one?",
"DBNotRunning": "Your LessPass Database is not running",
"DefaultOptionLocalStorage": "We use local storage to save default options locally. Each time you open the app, these options will be loaded by default.",
"Email": "Email",
"EmailAlreadyExist": "This email is already registered. Want to login or recover your password?",
"EmailInvalid": "Please enter a valid email",
"EmailRequiredError": "We need an email to find your account.",
"Encrypt my master password": "Encrypt my master password",
"ForgotPassword": "Forgot your password?",
"Generate": "Generate",
"Length": "Length",
"LessPass Database Url": "LessPass Database Url",
"Login": "Login",
"LoginFormInvalid": "LessPass URL, email, and password are mandatory",
"LoginIncorrectError": "The email and password you entered did not match our records. Please double-check and try again.",
"Master Password": "Master Password",
"Next": "Next",
"NoMatchFor": "Oops! There are no matches for",
"NoPassword": "You don't have any password profile saved in your database.",
"Password profile deleted": "Password profile deleted",
"PasswordProfileCopied": "Your password profile has been copied",
"PasswordResetRequired": "A password is required",
"PasswordResetSuccessful": "Your password was reset successfully.",
"Previous": "Previous",
"Register": "Register",
"Reset my password": "Reset my password",
"ResetLinkExpired": "This password reset link has expired.",
"Save options": "Save options",
"Sign In": "Sign In",
"Site": "Site",
"SiteLoginMasterPasswordMandatory": "Site, login, and master password fields are mandatory.",
"SorryCopy": "We are sorry the copy only works on modern browsers",
"UNDO": "UNDO",
"UpdateYourSearch": "Please try broadening your search.",
"Version": "Version",
"WarningV1Deprecated": "Version 1 is deprecated and will be deleted soon. We strongly advise you to migrate your passwords to version 2.",
"WelcomeRegister": "Welcome {email}, thank you for signing up.",
"Your options have been saved successfully": "Your options have been saved successfully",
"resetPasswordSuccess": "If the email address {email} is associated with a LessPass account, you will shortly receive an email from LessPass with instructions on how to reset your password.",
"version": "version",
"versionShortcut": "v"
}

+ 47
- 0
cordova/www/dist/i18n/es.json 查看文件

@@ -0,0 +1,47 @@
{
"Advanced options": "Opciones avanzadas",
"Copied": "¡ copiado !",
"Counter": "Contador",
"CounterFieldHelp": "Aumente este valor para cambiar la contraseña generada sin cambiar su contraseña maestra.",
"CreatePassword": "¿Quiere crear una?",
"DBNotRunning": "Su base de datos de LessPass no está ejecutando",
"DefaultOptionLocalStorage": "Usamos almacenamiento local para guardar las opciones predeterminadas de forma local. Cada vez que abre la aplicación, estas opciones se cargan de forma predeterminada",
"Email": "Correo electrónico",
"EmailAlreadyExist": "Este correo electrónico ya está registrado. ¿Quiere iniciar sesión o recuperar su contraseña?",
"EmailInvalid": "Ingrese un correo elecrónico válido",
"EmailRequiredError": "Necesitamos un correo electrónico para encontrar su cuenta.",
"Encrypt my master password": "Cifrar mi contraseña maestra",
"ForgotPassword": "¿Olvidó su contraseña?",
"Generate": "Generar",
"Length": "Tamaño",
"LessPass Database Url": "URL de la base de datos LessPass",
"Login": "Iniciar sesión",
"LoginFormInvalid": "URL, correo electrónico y contraseña de LessPass URL son obligatorios",
"LoginIncorrectError": "El correo electrónico y la contraseña que ingresó no concuerdan con nuestros registros. Revíselos de nuevo.",
"Master Password": "Contraseña maestra",
"Next": "Después",
"NoMatchFor": "¡Vaya! No ha resultados para",
"NoPassword": "No tiene ningún perfil de contraseñas guardado en su base de datos.",
"Password profile deleted": "Contraseña del perfil de borrado",
"PasswordProfileCopied": "Se ha copiado su perfil de contraseña",
"PasswordResetRequired": "Se requiere una contraseña",
"PasswordResetSuccessful": "Su contraseña ha sido reestablecida con éxito.",
"Previous": "Anterior",
"Register": "Registrar",
"Reset my password": "Reestablecer mi contraseña",
"ResetLinkExpired": "El enlace para reestablecer esta contraseña ha expirado.",
"Save options": "Guardar opciones",
"Sign In": "Registrarse",
"Site": "Sitio",
"SiteLoginMasterPasswordMandatory": "Los campos sitio, usuario y contraseña maestra son obligatorios.",
"SorryCopy": "Lamentamos que la copia sólo funcione en navegadores modernos",
"UNDO": "DESHACER",
"UpdateYourSearch": "Trate de ampliar su búsqueda.",
"Version": "Versión",
"WarningV1Deprecated": "La versión 1 está obsoleta y será eliminada pronto. Le recomendamos enérgicamente migrar sus contraseñas a la versión 2.",
"WelcomeRegister": "Bienvenido o bienvenida {email}, gracias por registrarse.",
"Your options have been saved successfully": "Sus opciones se han guardado correctamente",
"resetPasswordSuccess": "Si la dirección de correo electrónico {email} está asociada a una cuenta LessPass, recibirá un correo electrónico de LessPass con instrucciones sobre cómo restablecer su contraseña.",
"version": "versión",
"versionShortcut": "v"
}

+ 47
- 0
cordova/www/dist/i18n/fr.json 查看文件

@@ -0,0 +1,47 @@
{
"Advanced options": "Options avancées",
"Copied": "Copié !",
"Counter": "Compteur",
"CounterFieldHelp": "Augmenter cette valeur pour changer de mot de passe sans changer de mot de passe fort.",
"CreatePassword": "Voulez-vous en créer un ?",
"DBNotRunning": "Votre base de données LessPass n'est pas démarrée.",
"DefaultOptionLocalStorage": "Pour sauvegarder les options par default, nous utilisons le stockage du navigateur. Chaque fois que vous ouvrez l'application, ces options sont chargées par defaut.",
"Email": "Email",
"EmailAlreadyExist": "Cet email est déjà enregistré. Vous voulez peut-être vous connecter ?",
"EmailInvalid": "Entrez un email valide",
"EmailRequiredError": "Nous avons besoin d'un email pour trouver votre compte.",
"Encrypt my master password": "Chiffrer mon mot de passe fort",
"ForgotPassword": "Mot de passe oublié ?",
"Generate": "Générer",
"Length": "Longueur",
"LessPass Database Url": "Url de LessPass Database",
"Login": "Login",
"LoginFormInvalid": "L'URL LessPass, l'email et le mot de passe sont obligatoires.",
"LoginIncorrectError": "L'email et le mot de passe ne sont pas dans notre base de données. Vérifiez une nouvelle fois et réessayez.",
"Master Password": "Mot de passe fort",
"Next": "Suivant",
"NoMatchFor": "Oups ! il n'y a aucun resultat pour",
"NoPassword": "Vous n'avez aucun mot de passe enregistré.",
"Password profile deleted": "Profil de mot de passe supprimé",
"PasswordProfileCopied": "Votre profil de mot de passe a été copié",
"PasswordResetRequired": "Un mot de passe est requis",
"PasswordResetSuccessful": "Votre mot de passe a été changé avec succès",
"Previous": "Précédent",
"Register": "S'enregistrer",
"Reset my password": "Changer mon mot de passe",
"ResetLinkExpired": "Ce lien a expiré.",
"Save options": "Enregistrer les options",
"Sign In": "Se connecter",
"Site": "Site",
"SiteLoginMasterPasswordMandatory": "Les champs site, login et mot de passe fort sont obligatoires.",
"SorryCopy": "Nous sommes désolés, la copie ne fonctionne que sur les navigateurs modernes",
"UNDO": "ANNULER",
"UpdateYourSearch": "Merci de modifier votre recherche.",
"Version": "Version",
"WarningV1Deprecated": "La version 1 est déconseillée et sera supprimée bientôt. Nous vous conseillons fortement de migrer vos mots de passe vers la version 2.",
"WelcomeRegister": "Bienvenue {email}, merci pour vous être enregistré.",
"Your options have been saved successfully": "Vos options ont été enregistrées avec succès",
"resetPasswordSuccess": "Si l'adresse email {email} est associée avec un compte LessPass, vous allez recevoir un email de la part de LessPass avec les instructions pour changer votre mot de passe.",
"version": "version",
"versionShortcut": "v"
}

+ 47
- 0
cordova/www/dist/i18n/zh-CN.json 查看文件

@@ -0,0 +1,47 @@
{
"Advanced options": "高级选项",
"Copied": "已复制",
"Counter": "计数器",
"CounterFieldHelp": "增加这个值就可以在不改变主密码的前提下生成全新的密码。",
"CreatePassword": "您要生成一个密码吗?",
"DBNotRunning": "您的 LessPass 数据库没有运行",
"DefaultOptionLocalStorage": "我们使用“本地存储”在本机保存默认选项。每次您开启本应用时,会默认加载这些选项。",
"Email": "邮件地址",
"EmailAlreadyExist": "这个邮件地址已被注册。登录或找回您的密码?",
"EmailInvalid": "请输入一个有效的电子邮件地址",
"EmailRequiredError": "我们需要一个电子邮件地址来找到您的账户。",
"Encrypt my master password": "加密我的主密码",
"ForgotPassword": "忘记了您的密码?",
"Generate": "生成",
"Length": "长度",
"LessPass Database Url": "LessPass 数据库网址",
"Login": "登录名",
"LoginFormInvalid": "LessPass 网址、电子邮件地址以及密码均为必填信息。",
"LoginIncorrectError": "我们没找到符合您输入的电子邮件地址及密码的记录。请核验后再试。",
"Master Password": "主密码",
"Next": "下一步",
"NoMatchFor": "没有找到符合下列条件的内容:",
"NoPassword": "您的数据库里没有保存任何密码配置。",
"Password profile deleted": "密码配置文件被删",
"PasswordProfileCopied": "已复制您的密码配置。",
"PasswordResetRequired": "请输入登录密码",
"PasswordResetSuccessful": "已成功重置您的登录密码。",
"Previous": "上一步",
"Register": "注册",
"Reset my password": "重置我的登录密码",
"ResetLinkExpired": "此登录密码重置链接已过期。",
"Save options": "保存选项",
"Sign In": "登录",
"Site": "网站名",
"SiteLoginMasterPasswordMandatory": "网站名、登录名以及主密码均为必填信息。",
"SorryCopy": "很抱歉,但复制功能仅适用于现代浏览器",
"UNDO": "解开",
"UpdateYourSearch": "请尝试放宽您的搜索条件。",
"Version": "版本",
"WarningV1Deprecated": "版本 1 已不再支持,不久后将被删除。我们强烈建议您将密码迁移至版本 2。",
"WelcomeRegister": "你好 {email},欢迎您的注册。",
"Your options have been saved successfully": "您的选项已成功保存",
"resetPasswordSuccess": "如果电子邮件地址 {email} 与一个 LessPass 账户相关联,您将很快收到 LessPass 的电子邮件,里面提供有重置密码的操作说明。",
"version": "版本",
"versionShortcut": "v"
}

+ 47
- 0
cordova/www/dist/i18n/zh.json 查看文件

@@ -0,0 +1,47 @@
{
"Advanced options": "進階選項",
"Copied": "已複製",
"Counter": "計數器",
"CounterFieldHelp": "改變這個值,就可以在不改變主密碼的狀況下,產生新的密碼。",
"CreatePassword": "您要產生一組密碼嗎?",
"DBNotRunning": "您的 LessPass 資料庫並未執行",
"DefaultOptionLocalStorage": "我們將預設選項儲存在本機。每當您開啟程式,這些設定將會被自動載入。",
"Email": "郵件位址",
"EmailAlreadyExist": "這個郵件位址已被註冊。想要登入或取回您的密碼嗎?",
"EmailInvalid": "請輸入一個有效的郵件位址",
"EmailRequiredError": "我們需要郵件位址來找到您的帳號。",
"Encrypt my master password": "加密我的主密码",
"ForgotPassword": "忘記您的登入密碼了嗎?",
"Generate": "產生",
"Length": "長度",
"LessPass Database Url": "LessPass數據庫URL",
"Login": "登入帳號",
"LoginFormInvalid": "LessPass URL、郵件位址、登入密碼皆為必填欄位。",
"LoginIncorrectError": "我們查不到您輸入的郵件位址及登入密碼。請確認後再試一次。",
"Master Password": "主密碼",
"Next": "然後",
"NoMatchFor": "喔不!沒有找到跟下列條件相似的結果:",
"NoPassword": "您的資料庫內沒有儲存任何密碼。",
"Password profile deleted": "密碼配置文件被刪",
"PasswordProfileCopied": "您的密码配置文件已被复制。",
"PasswordResetRequired": "請輸入登入密碼",
"PasswordResetSuccessful": "已成功重置您的登入密碼。",
"Previous": "以前",
"Register": "註冊",
"Reset my password": "重置我的登入密碼",
"ResetLinkExpired": "此登入密碼重設連結已過期。",
"Save options": "保存選項",
"Sign In": "登入",
"Site": "網站位址",
"SiteLoginMasterPasswordMandatory": "網站位址、登入帳號、主密碼皆為必填欄位。",
"SorryCopy": "我們很抱歉,該副本僅適用於現代瀏覽器",
"UNDO": "解開",
"UpdateYourSearch": "請試著放寬您的搜尋條件。",
"Version": "版本",
"WarningV1Deprecated": "版本 1 已不支援,不久將被刪除。 我們強烈得建議您將密碼換至版本 2。",
"WelcomeRegister": "歡迎 {email},謝謝您的註冊。",
"Your options have been saved successfully": "您的選項已成功保存",
"resetPasswordSuccess": "如果电子邮件地址 {email} 与LessPass帐户相关联,您将很快收到LessPass的电子邮件,并提供如何重置密码的说明。",
"version": "版本",
"versionShortcut": "v"
}

+ 6
- 0
cordova/www/dist/lesspass.min.css
文件差异内容过多而无法显示
查看文件


+ 38
- 0
cordova/www/dist/lesspass.min.js
文件差异内容过多而无法显示
查看文件


二进制
cordova/www/icons/mipmap-hdpi/icon.png 查看文件

之前 之后
宽度: 72  |  高度: 72  |  大小: 2.3 KiB

二进制
cordova/www/icons/mipmap-ldpi/icon.png 查看文件

之前 之后
宽度: 36  |  高度: 36  |  大小: 1.2 KiB

二进制
cordova/www/icons/mipmap-mdpi/icon.png 查看文件

之前 之后
宽度: 48  |  高度: 48  |  大小: 1.5 KiB

二进制
cordova/www/icons/mipmap-xhdpi/icon.png 查看文件

之前 之后
宽度: 96  |  高度: 96  |  大小: 3.1 KiB

二进制
cordova/www/img/icon.png 查看文件

之前 之后
宽度: 1024  |  高度: 1024  |  大小: 49 KiB

二进制
cordova/www/img/screen.png 查看文件

之前 之后
宽度: 2048  |  高度: 2048  |  大小: 80 KiB

+ 20
- 0
cordova/www/index.html 查看文件

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:; connect-src *;">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport"
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
<link rel="stylesheet" href="dist/lesspass.min.css">
<title>LessPass</title>
</head>
<body>
<div class="lesspass--unbordered lesspass--full-width">
<div id="lesspass"></div>
</div>
<script type="text/javascript" src="cordova.js"></script>
<script src="dist/lesspass.min.js"></script>
</body>
</html>

+ 46
- 0
cordova/www/js/index.js 查看文件

@@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
var app = {
// Application Constructor
initialize: function() {
document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
},

// deviceready Event Handler
//
// Bind any cordova events here. Common events are:
// 'pause', 'resume', etc.
onDeviceReady: function() {
this.receivedEvent('deviceready');
},

// Update DOM on a Received Event
receivedEvent: function(id) {
var parentElement = document.getElementById(id);
var listeningElement = parentElement.querySelector('.listening');
var receivedElement = parentElement.querySelector('.received');

listeningElement.setAttribute('style', 'display:none;');
receivedElement.setAttribute('style', 'display:block;');

console.log('Received Event: ' + id);
}
};

app.initialize();

+ 0
- 1
core

@@ -1 +0,0 @@
Subproject commit b6ed2c7b03963404ea3e0ad622e19bf6bc468b2b

+ 17
- 0
core/.editorconfig 查看文件

@@ -0,0 +1,17 @@
# editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.py]
indent_size = 4

+ 2
- 0
core/.gitignore 查看文件

@@ -0,0 +1,2 @@
node_modules/
npm-debug.log

+ 17
- 0
core/.travis.yml 查看文件

@@ -0,0 +1,17 @@
dist: trusty
sudo: required
language: node_js
node_js:
- 6
addons:
firefox: "latest"
apt:
sources:
- google-chrome
packages:
- google-chrome-stable
before_script:
- export CHROME_BIN=/usr/bin/google-chrome
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3

+ 89
- 0
core/README.md 查看文件

@@ -0,0 +1,89 @@
# LessPass core

npm core library used to generate LessPass passwords

## Requirements

- node LTS v6

## Install

npm install lesspass

## Usage

var profile = {
site: 'example.org',
login: 'contact@example.org'
}
var masterPassword = 'password';
LessPass.generatePassword(profile, masterPassword)
.then(function (generatedPassword) {
console.log(generatedPassword); // WHLpUL)e00[iHR+w
});

see [tests/api.tests.js](tests/v2/api.tests.js) for more examples

## API

### generatePassword(profile, masterPassword)

generate LessPass password
var profile = {
site: 'example.org',
login: 'contact@example.org'
options: {
uppercase: true,
lowercase: true,
digits: true,
symbols: true,
length: 16,
counter: 1
},
crypto: {
method: 'pbkdf2',
iterations: 100000,
keylen: 32,
digest: "sha256"
}
};
var masterPassword = 'password';
LessPass.generatePassword(profile, masterPassword)
.then(function (generatedPassword) {
console.log(generatedPassword); // WHLpUL)e00[iHR+w
});

### createFingerprint(password)

create a fingerprint
LessPass.createFingerprint('password').then(fingerprint => {
console.log(fingerprint); //e56a207acd1e6714735487c199c6f095844b7cc8e5971d86c003a7b6f36ef51e
});

### isSupported()

test if LessPass is supported

LessPass.isSupported().then(function(isSupported) {
if (isSupported) {
console.log("LessPass is supported");
}
else {
console.log("LessPass is not supported");
}
});

## Tests

npm test

## License

This project is licensed under the terms of the GNU GPLv3.


## Issues

report issues on [LessPass project](https://github.com/lesspass/lesspass/issues)

+ 3873
- 0
core/dist/lesspass.js
文件差异内容过多而无法显示
查看文件


+ 1
- 0
core/dist/lesspass.min.js
文件差异内容过多而无法显示
查看文件


+ 49
- 0
core/example/index.html 查看文件

@@ -0,0 +1,49 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="../dist/lesspass.min.js"></script>
<script>
function displayInBody(txt) {
document.body.innerHTML = document.body.innerHTML + txt + '<br>';
}

function displayErrorInBody(txt) {
document.body.innerHTML = document.body.innerHTML + '<span style="color:red;">' + txt + '</span><br>';
}

function createFingerprint() {
LessPass.createFingerprint('password').then(fingerprint => {
displayInBody("createFingerprint :");
displayInBody("e56a207acd1e6714735487c199c6f095844b7cc8e5971d86c003a7b6f36ef51e");
displayInBody(fingerprint + " (generated)")
});
}

function generatePassword() {
const profile = {
site: 'example.org',
login: 'contact@example.org'
}
const masterPassword = 'password';
LessPass.generatePassword(profile, masterPassword).then(function(generatedPassword) {
displayInBody("<br>generatePassword :");
displayInBody("WHLpUL)e00[iHR+w");
displayInBody(generatedPassword + " (generated)")
});
}

LessPass.isSupported().then(function(isSupported) {
if (isSupported) {
createFingerprint();
generatePassword();
}
else {
displayErrorInBody("LessPass is not supported on your browser!");
}
});
</script>
</body>
</html>

+ 3673
- 0
core/package-lock.json
文件差异内容过多而无法显示
查看文件


+ 57
- 0
core/package.json 查看文件

@@ -0,0 +1,57 @@
{
"name": "lesspass",
"version": "8.0.1",
"description": "LessPass node module used to generate LessPass passwords",
"keywords": [
"crypto",
"lesspass",
"password"
],
"license": "GPL-3.0",
"author": "Guillaume Vincent <guillaume@oslab.fr>",
"files": [
"dist",
"src"
],
"main": "src/lesspass.js",
"browser": {
"./src/pbkdf2.js": "./src/pbkdf2.browser.js",
"./src/hmac.js": "./src/hmac.browser.js"
},
"module": "src/lesspass.js",
"jsnext:main": "src/lesspass.js",
"repository": "lesspass/core",
"scripts": {
"precommit": "npm run test:unit && lint-staged",
"clean": "rm -rf dist && mkdir dist && npm prune",
"build": "npm run clean && browserify --standalone LessPass src/lesspass.js > dist/lesspass.js && npm run minify",
"minify": "uglifyjs --output dist/lesspass.min.js --compress --mangle -- dist/lesspass.js",
"test": "npm run test:unit && npm run test:browser",
"test:unit": "mocha test --recursive",
"test:browser": "npm run build && karma start test/karma.conf.js"
},
"dependencies": {
"lesspass-render-password": "^0.1.0",
"lodash.merge": "^4.6.0",
"unibabel": "2.1.4"
},
"devDependencies": {
"browserify": "^14.3.0",
"husky": "^0.14.3",
"karma": "^1.6.0",
"karma-browserify": "^5.1.1",
"karma-chrome-launcher": "^2.0.0",
"karma-firefox-launcher": "^1.0.1",
"karma-mocha": "^1.3.0",
"lint-staged": "^4.3.0",
"mocha": "^4.0.1",
"prettier": "^1.2.2",
"uglify-js": "^3.0.1"
},
"lint-staged": {
"{src,test}/**/*.js": [
"prettier --write",
"git add"
]
}
}

+ 31
- 0
core/src/hmac.browser.js 查看文件

@@ -0,0 +1,31 @@
require("unibabel");
require("unibabel/unibabel.hex");

module.exports = function(digest, string, salt) {
var algorithms = {
sha1: "SHA-1",
"sha-1": "SHA-1",
sha256: "SHA-256",
"sha-256": "SHA-256",
sha512: "SHA-512",
"sha-512": "SHA-512"
};
return window.crypto.subtle
.importKey(
"raw",
Unibabel.utf8ToBuffer(string),
{
name: "HMAC",
hash: { name: algorithms[digest.toLowerCase()] }
},
true,
["sign", "verify"]
)
.then(function(key) {
return window.crypto.subtle
.sign({ name: "HMAC" }, key, Unibabel.utf8ToBuffer(salt || ""))
.then(function(signature) {
return Unibabel.bufferToHex(new Uint8Array(signature));
});
});
};

+ 8
- 0
core/src/hmac.js 查看文件

@@ -0,0 +1,8 @@
var crypto = require("crypto");

module.exports = function(digest, string, salt) {
return new Promise(function(resolve) {
var hmac = crypto.createHmac(digest, string);
resolve(hmac.update(salt || "").digest("hex"));
});
};

+ 69
- 0
core/src/lesspass.js 查看文件

@@ -0,0 +1,69 @@
var hmac = require("./hmac");
var pbkdf2 = require("./pbkdf2");
var merge = require("lodash.merge");
var renderPassword = require("lesspass-render-password");

var defaultProfile = {
site: "",
login: "",
options: {
uppercase: true,
lowercase: true,
digits: true,
symbols: true,
length: 16,
counter: 1
},
crypto: {
method: "pbkdf2",
iterations: 100000,
keylen: 32,
digest: "sha256"
}
};

module.exports = {
generatePassword: generatePassword,
createFingerprint: createFingerprint,
isSupported: isSupported,
_calcEntropy: _calcEntropy
};

function generatePassword(profile, masterPassword) {
var _profile = merge({}, defaultProfile, profile);
return _calcEntropy(_profile, masterPassword).then(function(entropy) {
return renderPassword(entropy, _profile.options);
});
}

function createFingerprint(str) {
return hmac("sha256", str);
}

function isSupported() {
try {
var simpleProfile = merge({}, defaultProfile, {
crypto: { iterations: 1 }
});
return generatePassword(simpleProfile, "LessPass").then(function(
generatedPassword
) {
return generatedPassword === "n'LTsjPA#3E$e*2'";
});
} catch (e) {
console.error(e);
return Promise.resolve(false);
}
}

function _calcEntropy(profile, masterPassword) {
var salt =
profile.site + profile.login + profile.options.counter.toString(16);
return pbkdf2(
masterPassword,
salt,
profile.crypto.iterations,
profile.crypto.keylen,
profile.crypto.digest
);
}

+ 42
- 0
core/src/pbkdf2.browser.js 查看文件

@@ -0,0 +1,42 @@
require("unibabel");
require("unibabel/unibabel.hex");

module.exports = function(password, salt, iterations, keylen, digest) {
var algorithms = {
sha1: "SHA-1",
"sha-1": "SHA-1",
sha256: "SHA-256",
"sha-256": "SHA-256",
sha512: "SHA-512",
"sha-512": "SHA-512"
};
return window.crypto.subtle
.importKey("raw", Unibabel.utf8ToBuffer(password), "PBKDF2", false, [
"deriveKey"
])
.then(function(key) {
var algo = {
name: "PBKDF2",
salt: Unibabel.utf8ToBuffer(salt),
iterations: iterations,
hash: algorithms[digest.toLowerCase()]
};
return window.crypto.subtle.deriveKey(
algo,
key,
{
name: "AES-CTR",
length: keylen * 8
},
true,
["encrypt", "decrypt"]
);
})
.then(function(derivedKey) {
return window.crypto.subtle
.exportKey("raw", derivedKey)
.then(function(keyArray) {
return Unibabel.bufferToHex(new Uint8Array(keyArray));
});
});
};

+ 16
- 0
core/src/pbkdf2.js 查看文件

@@ -0,0 +1,16 @@
const crypto = require("crypto");

module.exports = function(password, salt, iterations, keylen, digest) {
return new Promise(function(resolve, reject) {
crypto.pbkdf2(password, salt, iterations, keylen, digest, function(
error,
key
) {
if (error) {
reject("error in pbkdf2");
} else {
resolve(key.toString("hex"));
}
});
});
};

+ 98
- 0
core/test/api.tests.js 查看文件

@@ -0,0 +1,98 @@
var assert = require("assert");
var LessPass = require("../src/lesspass");

describe("api", () => {
it("generatePassword", () => {
const profile = {
site: "example.org",
login: "contact@example.org",
options: {
lowercase: true,
uppercase: true,
digits: true,
symbols: true,
length: 16,
counter: 1
}
};
const masterPassword = "password";
return LessPass
.generatePassword(profile, masterPassword)
.then(generatedPassword => {
assert.equal("WHLpUL)e00[iHR+w", generatedPassword);
});
});
it("generatePassword default options", () => {
const profile = {
site: "example.org",
login: "contact@example.org"
};
const masterPassword = "password";
return LessPass.generatePassword(profile, masterPassword).then(generatedPassword => {
assert.equal("WHLpUL)e00[iHR+w", generatedPassword);
});
});
it("generatedPassword different options", () => {
const profile = {
site: "example.org",
login: "contact@example.org",
options: {
lowercase: true,
uppercase: true,
digits: true,
symbols: false,
length: 14,
counter: 2
}
};
const masterPassword = "password";
return LessPass.generatePassword(profile, masterPassword).then(generatedPassword => {
assert.equal("MBAsB7b1Prt8Sl", generatedPassword);
assert.equal(14, generatedPassword.length);
});
});
it("generatedPassword only digits", () => {
const profile = {
site: "example.org",
login: "contact@example.org",
options: {
lowercase: false,
uppercase: false,
digits: true,
symbols: false,
length: 6,
counter: 3
}
};
const masterPassword = "password";
return LessPass.generatePassword(profile, masterPassword).then(generatedPassword => {
assert.equal("117843", generatedPassword);
});
});
it("generatedPassword no digit", () => {
const profile = {
site: "example.org",
login: "contact@example.org",
options: {
digits: false
}
};
const masterPassword = "password";
return LessPass.generatePassword(profile, masterPassword).then(generatedPassword => {
assert.equal("s>{F}RwkN/-fmM.X", generatedPassword);
});
});
it("createFingerprint", () => {
return LessPass.createFingerprint("password").then(function(fingerprint) {
assert.equal(
"e56a207acd1e6714735487c199c6f095844b7cc8e5971d86c003a7b6f36ef51e",
fingerprint
);
});
});
it("isSupported", () => {
return LessPass.isSupported("password").then(function(isSupported) {
assert(isSupported);
});
});
});

+ 109
- 0
core/test/entropy.tests.js 查看文件

@@ -0,0 +1,109 @@
var assert = require("assert");
var LessPass = require("../src/lesspass");

describe("entropy", function() {
it("calc entropy pbkdf2 with default params (100000 iterations, 32 bytes length, sha256 digest)", function() {
const profile = {
site: "example.org",
login: "contact@example.org",
options: {
counter: 1
},
crypto: {
method: "pbkdf2",
iterations: 100000,
keylen: 32,
digest: "sha256"
}
};
const masterPassword = "password";
return LessPass._calcEntropy(profile, masterPassword).then(function(
entropy
) {
assert.equal(
"dc33d431bce2b01182c613382483ccdb0e2f66482cbba5e9d07dab34acc7eb1e",
entropy
);
});
});
it("calc entropy pbkdf2 with unicode char", function() {
const profile = {
site: "example.org",
login: "❤",
options: {
counter: 1
},
crypto: {
method: "pbkdf2",
iterations: 100000,
keylen: 32,
digest: "sha256"
}
};
const masterPassword = "I ❤ LessPass";
return LessPass._calcEntropy(profile, masterPassword).then(function(
entropy
) {
assert.equal(
"4e66cab40690c01af55efd595f5963cc953d7e10273c01827881ebf8990c627f",
entropy
);
});
});
it("calc entropy with different options (8192 iterations, 16 bytes length, sha512 digest)", function() {
const profile = {
site: "example.org",
login: "contact@example.org",
options: {
counter: 1
},
crypto: {
method: "pbkdf2",
iterations: 8192,
keylen: 16,
digest: "sha512"
}
};
const masterPassword = "password";
return LessPass._calcEntropy(profile, masterPassword).then(function(
entropy
) {
assert.equal("fff211c16a4e776b3574c6a5c91fd252", entropy);
});
});
it("calc entropy different if counter different 1", function() {
const profile = {
site: "example.org",
login: "contact@example.org",
options: {
counter: 1
},
crypto: {
method: "pbkdf2",
iterations: 100000,
keylen: 32,
digest: "sha256"
}
};
const profile2 = {
site: "example.org",
login: "contact@example.org",
options: {
counter: 2
},
crypto: {
method: "pbkdf2",
iterations: 100000,
keylen: 32,
digest: "sha256"
}
};
const promises = [
LessPass._calcEntropy(profile, "password"),
LessPass._calcEntropy(profile2, "password")
];
Promise.all(promises).then(values => {
assert.notEqual(values[0], values[1]);
});
});
});

+ 21
- 0
core/test/hmac.tests.js 查看文件

@@ -0,0 +1,21 @@
var assert = require("assert");
var createHmac = require("../src/hmac");

describe("hmac", function() {
it("createHmac", function() {
return createHmac("sha256", "password").then(function(fingerprint) {
assert.equal(
"e56a207acd1e6714735487c199c6f095844b7cc8e5971d86c003a7b6f36ef51e",
fingerprint
);
});
});
it("createHmac and update", function() {
return createHmac("sha256", "password", "salt").then(function(fingerprint) {
assert.equal(
"fc328232993ff34ca56631e4a101d60393cad12171997ee0b562bf7852b2fed0",
fingerprint
);
});
});
});

+ 19
- 0
core/test/karma.conf.js 查看文件

@@ -0,0 +1,19 @@
module.exports = function(config) {
config.set({
basePath: "..",
frameworks: ["browserify", "mocha"],
files: ["dist/lesspass.min.js", "test/**/*.js"],
exclude: [],
preprocessors: {
"test/**/*.js": ["browserify"]
},
reporters: ["progress"],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ["Firefox", "Chrome"],
singleRun: true,
concurrency: Infinity
});
};

+ 227
- 0
core/test/pbkdf2.tests.js 查看文件

@@ -0,0 +1,227 @@
var assert = require("assert");
var pbkdf2 = require("../src/pbkdf2");

describe("pbkdf2", function() {
it("secret, salt, 2 iterations, 32 keylen, sha256 hash", function() {
return pbkdf2("secret", "salt", 2, 32, "sha256").then(function(key) {
assert.equal(
"f92f45f9df4c2aeabae1ed3c16f7b64660c1f8e377fa9b4699b23c2c3a29f569",
key
);
});
});
it("use pbkdf2 with 8192 iterations and sha256", function() {
return pbkdf2(
"password",
"test@example.org",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"d8af5f918db6b65b1db3d3984e5a400e39e1dbb19462220e4431de283809f472",
key
);
});
});
it("customize number of iterations", function() {
return pbkdf2(
"password",
"test@example.org",
4096,
32,
"sha256"
).then(function(key) {
assert.equal(
"0a91208545e3aa4935d3a22984ca097a7669259a04d261ac16361bdc1a2e960f",
key
);
});
});
it("customize key length", function() {
return pbkdf2(
"password",
"test@example.org",
8192,
16,
"sha256"
).then(function(key) {
assert.equal("d8af5f918db6b65b1db3d3984e5a400e", key);
});
});
it("customize iterations and key length", function() {
return pbkdf2(
"password",
"test@example.org",
4096,
16,
"sha256"
).then(function(key) {
assert.equal("0a91208545e3aa4935d3a22984ca097a", key);
});
});
it("utf8 parameter", function() {
return pbkdf2(
"♥ LessPass ♥",
"test@example.org",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"997fe81d3d0db236e039c75efdb487f17a902fdf94f9dacaa9884329c85d9651",
key
);
});
});
it("auto generated test 0", function() {
return pbkdf2(
"password",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0",
key
);
});
});
it("auto generated test 1", function() {
return pbkdf2(
"password",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0",
key
);
});
});
it("auto generated test 2", function() {
return pbkdf2(
"password",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0",
key
);
});
});
it("auto generated test 3", function() {
return pbkdf2(
"password",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0",
key
);
});
});
it("auto generated test 4", function() {
return pbkdf2(
"password",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0",
key
);
});
});
it("auto generated test 5", function() {
return pbkdf2(
"password",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0",
key
);
});
});
it("auto generated test 6", function() {
return pbkdf2(
"password",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0",
key
);
});
});
it("auto generated test 7", function() {
return pbkdf2(
"password",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0",
key
);
});
});
it("auto generated test 8", function() {
return pbkdf2(
"password",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0",
key
);
});
});
it("auto generated test 9", function() {
return pbkdf2("password", "lesspass", 8192, 32, "sha256").then(function(
key
) {
assert.equal(
"7d05ee25597dcc3ac16d082aa910e7707f75be620ed8db5bef7245e2a8579116",
key
);
});
});
it("auto generated test 10", function() {
return pbkdf2(
"password2",
"contact@lesspass.com",
8192,
32,
"sha256"
).then(function(key) {
assert.equal(
"ce853092fc54fe88c281e38df97bd5826d64e6bee315dc94939cbba8930df0e4",
key
);
});
});
});

+ 0
- 1
cozy

@@ -1 +0,0 @@
Subproject commit 0b416c5cf19fe912b186c90f250734a987e2a1ac

+ 17
- 0
cozy/.editorconfig 查看文件

@@ -0,0 +1,17 @@
# editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.py]
indent_size = 4

+ 1
- 0
cozy/.gitignore 查看文件

@@ -0,0 +1 @@
node_modules

+ 24
- 0
cozy/README.md 查看文件

@@ -0,0 +1,24 @@
# LessPass cozy

LessPass for MyCozyCloud

## Install

Install app with `https://github.com/lesspass/cozy@master` repository

## Build

npm run build

## Run

npm start

## License

This project is licensed under the terms of the GNU GPLv3.


## Issues

report issues on [LessPass project](https://github.com/lesspass/lesspass/issues)

二进制
cozy/dist/674f50d287a8c48dc19ba404d20fe713.eot 查看文件


+ 2671
- 0
cozy/dist/912ec66d7572ff821749319396470bde.svg
文件差异内容过多而无法显示
查看文件


二进制
cozy/dist/af7ae505a9eed503f8b8e6982036873e.woff2 查看文件


二进制
cozy/dist/b06871f281fee6b241d60582ae9369b9.ttf 查看文件


部分文件因为文件数量过多而无法显示

正在加载...
取消
保存