Refactor README into several documents unders docs directory. (#265)

pull/264/merge
Clément Michaud 2018-08-26 23:46:15 +02:00 committed by GitHub
parent cf89aa909c
commit a515ce83c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 412 additions and 260 deletions

299
README.md
View File

@ -1,290 +1,71 @@
# Authelia <p align="center">
<img src="images/authelia-title.png" width="350" title="Authelia">
</p>
[![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)][MIT License] [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)][MIT License]
[![Build](https://travis-ci.org/clems4ever/authelia.svg?branch=master)](https://travis-ci.org/clems4ever/authelia) [![Build](https://travis-ci.org/clems4ever/authelia.svg?branch=master)](https://travis-ci.org/clems4ever/authelia)
[![Gitter](https://img.shields.io/gitter/room/badges/shields.svg)](https://gitter.im/authelia/general?utm_source=share-link&utm_medium=link&utm_campaign=share-link) [![Slack](https://img.shields.io/badge/chat-on--slack-green.svg?longCache=true&style=flat)](https://authelia.slack.com/messages/C3YBS84NL)
**Authelia** is an open-source authentication and authorization server. **Authelia** is an open-source authentication and authorization providing
Authelia brings 2-factor authentication and single sign-on to secure web 2-factor authentication and single sign-on (SSO) for your applications.
applications and ease authentication. It has been designed to be a companion It acts as a companion of reverse proxies by handling authentication and
of any reverse proxy by helping it handle authentication and authorization authorization requests.
requests.
**Authelia** can be installed on bare-metal using Docker or npm but can also **Authelia** can be installed as a standalone service using Docker or NPM
be deployed easily on a Kubernetes cluster and leverages ingress controller but can also be deployed easily on Kubernetes. On the latest, one can
and ingress configuration concepts to easily configure authentication and leverage ingress configuration to set up authentication and authorizations
authorization for specific services in your cluster by simply editing your for specific services in only few seconds.
ingress configuration.
<p align="center">
# Table of Contents <img src="images/first_factor.png" width="400">
1. [Features summary](#features-summary) <img src="images/second_factor.png" width="400">
2. [Deployment](#deployment) </p>
1. [With NPM](#with-npm)
2. [With Docker](#with-docker)
3. [With Kubernetes](#with-docker)
3. [Getting started](#getting-started)
1. [Pre-requisites](#pre-requisites)
2. [Run it!](#run-it)
4. [Features in details](#features-in-details)
1. [First factor with LDAP and ACL](#first-factor-with-ldap-and-acl)
2. [Second factor with TOTP](#second-factor-with-totp)
3. [Second factor with U2F security keys](#second-factor-with-u2f-security-keys)
4. [Password reset](#password-reset)
5. [Access control](#access-control)
6. [Single factor authentication](#single-factor-authentication)
7. [Session management with Redis](#session-management-with-redis)
4. [Security](#security)
5. [Documentation](#documentation)
1. [Authelia configuration](#authelia-configuration)
2. [Wiki](#wiki)
3. [API documentation](#api-documentation)
6. [Contributing to Authelia](#contributing-to-authelia)
7. [License](#license)
---
## Features summary ## Features summary
* Two-factor authentication using either
**[TOTP] - Time-Base One Time password -** or **[U2F] - Universal 2-Factor -** Here is the list of the main available features:
as 2nd factor.
* **[U2F] - Universal 2-Factor -** support with [Yubikey].
* **[TOTP] - Time-Base One Time password -** support with [Google Authenticator].
* Password reset with identity verification using email. * Password reset with identity verification using email.
* Single and two factors authentication methods available. * Single-factor only authentication method available.
* Access restriction after too many authentication attempts. * Access restriction after too many authentication attempts.
* User-defined access control per subdomain and resource. * User-defined access control per subdomain and resource.
* Support of [basic authentication] for endpoints protected by single factor. * Support of [basic authentication] for endpoints protected by single factor.
* High-availability using a highly-available distributed database and KV store. * High-availability using distributed database and KV store.
* Compatible with Kubernetes ingress-nginx controller out of the box. * Compatible with Kubernetes ingress-nginx controller out of the box.
## Deployment For more details about the features, follow [Features](./docs/features.md).
If you don't have any LDAP and/or nginx setup yet, I advise you to follow the ## Getting Started
[Getting Started](#Getting-started) section. That way, you can test it right away
without even configuring anything.
Otherwise, here are the available steps to deploy **Authelia** on your machine given Follow [Getting Started](./docs/getting_started.md).
your configuration file is **/path/to/your/config.yml**. Note that you can create your
own the configuration file from [config.template.yml] at the root of the repo.
### With NPM
npm install -g authelia
authelia /path/to/your/config.yml
### With Docker
docker pull clems4ever/authelia
docker run -v /path/to/your/config.yml:/etc/authelia/config.yml -v /path/to/data/dir:/var/lib/authelia clems4ever/authelia
where **/path/to/data/dir** is the directory where all user data will be stored.
### With Kubernetes
<img src="/images/kube-logo.png" width="24" align="left">
Please refer to the following [README](./example/kube/README.md).
## Getting started
The provided example is docker-based so that you can deploy and test it very
quickly.
### Pre-requisites
#### npm
Make sure you have npm and node installed on your computer.
#### Docker
Make sure you have **docker** and **docker-compose** installed on your machine.
For your information, here are the versions that have been used for testing:
docker --version
gave *Docker version 17.03.1-ce, build c6d412e*.
docker-compose --version
gave *docker-compose version 1.14.0, build c7bdf9e*.
#### Available port
Make sure you don't have anything listening on port 8080 (webserver) and 8085 (webmail).
#### Subdomain aliases
Add the following lines to your **/etc/hosts** to alias multiple subdomains so that nginx can redirect request to the correct virtual host.
127.0.0.1 home.example.com
127.0.0.1 public.example.com
127.0.0.1 dev.example.com
127.0.0.1 admin.example.com
127.0.0.1 mx1.mail.example.com
127.0.0.1 mx2.mail.example.com
127.0.0.1 single_factor.example.com
127.0.0.1 login.example.com
### Run it!
Deploy the **Authelia** example with one of the following commands:
Build Docker container from current commit:
./scripts/build-dev.sh
./scripts/example-commit/deploy-example.sh
Use provided container on [DockerHub](https://hub.docker.com/r/clems4ever/authelia/):
./scripts/example-dockerhub/deploy-example.sh
After few seconds the services should be running and you should be able to visit
[https://home.example.com:8080/](https://home.example.com:8080/).
When accessing the login page, a self-signed certificate exception should appear,
it has to be trusted before you can get to the target page. The certificate
must also be trusted for each subdomain, therefore it is normal to see the exception
several times.
Below is what the login page looks like:
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/first_factor.png" width="400">
## Features in details
### First factor using an LDAP server
**Authelia** uses an LDAP server as the backend for storing credentials.
When authentication is needed, the user is redirected to the login page which
corresponds to the first factor. Authelia tries to bind the username and password
against the configured LDAP backend.
You can find an example of the configuration of the LDAP backend in [config.template.yml].
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/second_factor.png" width="400">
### Second factor with TOTP
In **Authelia**, you can register a per user TOTP (Time-Based One Time Password) secret before
authenticating. To do that, you need to click on the register button. It will
send a link to the user email address stored in LDAP. Since this is an example, the email is sent
to a fake email address you can access from the webmail at [http://localhost:8085](http://localhost:8085).
Click on **Continue** and you'll get your secret in QRCode and Base32 formats. You can use
[Google Authenticator]
to store them and get the generated tokens with the app.
**Note:** If you're testing with **npm**, you will not have access to the fake webmail. You can use the filesystem notifier (option available [config.template.yml]) that will create a file containing the validation URL instead of sending an email. Please only use it for testing.
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/totp.png" width="400">
### Second factor with U2F security keys
**Authelia** also offers authentication using U2F (Universal 2-Factor) devices like [Yubikey](Yubikey)
USB security keys. U2F is one of the most secure authentication protocol and is
already available for Google, Facebook, Github accounts and more.
Like TOTP, U2F requires you register your security key before authenticating.
To do so, click on the register button. This will send a link to the
user email address. Since this is an example, the email is sent
to a fake email address you can access from the webmail at [http://localhost:8085](http://localhost:8085).
Click on **Continue** and you'll be asking to touch the token of your device
to register. Upon successful registration, you can authenticate using your U2F
device by simply touching the token. Easy, right?!
**Note:** If you're testing with **npm**, you will not have access to the fake webmail. You can use the filesystem notifier (option available [config.template.yml]) that will create a file containing the validation URL instead of sending an email. Please only use it for testing.
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/u2f.png" width="400">
### Password reset
With **Authelia**, you can also reset your password in no time. Click on the
**Forgot password?** link in the login page, provide the username of the user requiring
a password reset and **Authelia** will send an email with an link to the user
email address. For the sake of the example, the email is delivered in a fake webmail deployed
for you and accessible at [http://localhost:8085](http://localhost:8085).
Paste the link in your browser and you should be able to reset the password.
**Note:** If you're testing with **npm**, you will not have access to the fake webmail. You can use the filesystem notifier (option available [config.template.yml]) that will create a file containing the validation URL instead of sending an email. Please only use it for testing.
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/reset_password.png" width="400">
### Access Control
With **Authelia**, you can define your own access control rules for finely restricting
user access to some resources and subdomains. Those rules are defined and fully documented
in the configuration file. They can apply to users, groups or everyone.
Check out [config.template.yml] to see how they are defined.
### Single factor authentication
Authelia allows you to customize the authentication method to use for each
sub-domain.The supported methods are either "single_factor" or "two_factor".
Please see [config.template.yml] to see an example of configuration.
It is also possible to use [basic authentication] to access a resource
protected by a single factor.
### Session management with Redis
When your users authenticate against Authelia, sessions are stored in a Redis key/value store. You can specify your own Redis instance in [config.template.yml].
## Security ## Security
### Protection against cookie theft If you want more information about the security measures applied by
**Authelia** and some tips on how to set up **Authelia** in a secure way,
refer to [Security](./docs/security.md).
Authelia uses two mechanism to protect against cookie theft: ## Deployment
1. session attribute `httpOnly` set to true make client-side code unable to
read the cookie.
2. session attribute `secure` ensure the cookie will never be sent over an
unsecure HTTP connections.
### Protection against multi-domain cookie attacks To learn how to deploy **Authelia** or use it on Kubernetes, please follow
[Deployment](./docs/deployment.md).
Since Authelia uses multi-domain cookies to perform single sign-on, an ## Build Authelia
attacker who poisonned a user's DNS cache can easily retrieve the user's
cookies by making the user send a request to one of the attacker's IPs.
To mitigate this risk, it's advisable to only use HTTPS connections with valid Follow [Build](./docs/build.md).
certificates and enforce it with HTTP Strict Transport Security ([HSTS]) so
that the attacker must also require the certificate to retrieve the cookies.
Note that using [HSTS] has consequences. That's why you should read the blog ## Changelog
post nginx has written on [HSTS].
### More protections measures See [Changelog](CHANGELOG.md).
You can also apply the following headers to your nginx configuration for ## Contributing
improving security. Please read the documentation of those headers before
applying them blindly.
``` Follow [Contributing](CONTRIBUTORS.md).
# We don't want any credentials / TOTP secret key / QR code to be cached by
# the client
add_header Cache-Control "no-store";
add_header Pragma "no-cache";
# Clickjacking / XSS protection
# We don't want Authelia's login page to be rendered within a <frame>,
# <iframe> or <object> from an external website.
add_header X-Frame-Options "SAMEORIGIN";
# Block pages from loading when they detect reflected XSS attacks.
add_header X-XSS-Protection "1; mode=block";
```
## Documentation
### Authelia configuration
The configuration of the server is defined in the file
[config.template.yml]. All the details are documented there.
You can specify another configuration file by giving it as first argument of
**Authelia**.
authelia config.custom.yml
### Wiki
A [wiki](../../wiki) is available if you need more details about Authelia.
### API documentation
There is a complete API documentation generated with
[apiDoc](http://apidocjs.com/) and embedded in the repo under the **doc/**
directory. Simply open index.html locally to watch it.
## Contributing to Authelia
Follow [contributing](CONTRIBUTORS.md) file.
## License ## License
**Authelia** is **licensed** under the **[MIT License]**. The terms of the license are as follows: **Authelia** is **licensed** under the **[MIT License]**. The terms of the license are as follows:
The MIT License (MIT) The MIT License (MIT)
@ -316,5 +97,3 @@ Follow [contributing](CONTRIBUTORS.md) file.
[auth_request]: http://nginx.org/en/docs/http/ngx_http_auth_request_module.html [auth_request]: http://nginx.org/en/docs/http/ngx_http_auth_request_module.html
[Google Authenticator]: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en [Google Authenticator]: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en
[config.template.yml]: https://github.com/clems4ever/authelia/blob/master/config.template.yml [config.template.yml]: https://github.com/clems4ever/authelia/blob/master/config.template.yml
[HSTS]: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
[basic authentication]: https://en.wikipedia.org/wiki/Basic_access_authentication

69
docs/build.md 100644
View File

@ -0,0 +1,69 @@
# Build
**Authelia** is written in Typescript and built with Grunt.
In order to build **Authelia**, you need to make sure Node v8 and NPM is
installed on your machine.
Then, run the following command install the node modules:
npm install
And, this command to build **Authelia** under dist/:
./node_modules/.bin/grunt build
## Details
### Build
**Authelia** is made of two components: the client and the server.
The client is written in Typescript and uses jQuery. It is built as part of
the global `build` Grunt command.
The server is written in Typescript. It is built as part of the global `build`
Grunt command.
### Tests
Grunt also handles the commands to run the tests. There are several type of
tests for **Authelia**: unit tests for the server, unit tests for the client
and integration tests for both.
The unit tests are written with Mocha while integration tests are using
Cucumber and Mocha.
### Unit tests
To run the client unit tests, run:
./node_modules/.bin/grunt test-client
To run the server unit tests, run:
./node_modules/.bin/grunt test-server
### Integration tests
Integration tests are mainly based on Selenium so they
need a complete environment to be set up.
Start by making sure **Authelia** is built with:
grunt build
and the docker image is built with:
./scripts/example-commit/dc-example.sh build
Then, start the environment with:
./scripts/example-commit/dc-example.sh up -d
And run the tests with:
./node_modules/.bin/grunt test-int
Note: the Cucumber tests are hard to maintain and will therefore
be refactored to use Mocha instead.

View File

@ -0,0 +1,11 @@
# Configuration
Authelia is highly configurable thanks to a configuration file.
There is a documented template configuration, called [config.template.yml], at
the root of the repository. All the details are documented there.
When running **Authelia**, you can specify your configuration file by passing
the file path as the first argument of **Authelia**.
authelia config.custom.yml

43
docs/deployment.md 100644
View File

@ -0,0 +1,43 @@
# Deployment
**Authelia** can be deployed in two different ways: npm and docker.
Here are the available steps to deploy **Authelia** on your machine given
your configuration file is **/path/to/your/config.yml**. Note that you can
create your own the configuration file from [config.template.yml] at the root
of the repo.
## Standalone
**Authelia** has been designed to be a proxy companion handling the SSO.
Therefore, deploying it in production means having an LDAP, a Redis, a
MongoDB and one or more nginx running and configured to be used with
Authelia.
If you don't have all of this, don't worry, there is a way to deploy
**Authelia** with only an nginx. To do so, please refer to the
[Getting Started]. Otherwise here are the command to run Authelia in your
environment.
### With NPM
npm install -g authelia
authelia /path/to/your/config.yml
### With Docker
docker pull clems4ever/authelia
docker run -v /path/to/your/config.yml:/etc/authelia/config.yml clems4ever/authelia
## Kubernetes
<img src="/images/kube-logo.png" width="24" align="left">
**Authelia** can also be used on top of Kubernetes using the nginx ingress
controller.
Please refer to the following [README](../example/kube/README.md) for more
information.
[config.template.yml]: ../config.template.yml

93
docs/features.md 100644
View File

@ -0,0 +1,93 @@
# Features in details
## First factor using a LDAP server
**Authelia** uses an LDAP server as the backend for storing credentials.
When authentication is needed, the user is redirected to the login page which
corresponds to the first factor. **Authelia** tries to bind the username and
password against the configured LDAP backend.
You can find an example of the configuration of the LDAP backend in
[config.template.yml].
<p align="center">
<img src="../images/second_factor.png" width="400">
</p>
## Second factor with TOTP
In **Authelia**, you can register a per user TOTP (Time-Based One Time
Password) secret before being being able to authenticate. Click on the
register button and check the email **Authelia** sent to your email address
to validate your identity.
Confirm your identity by clicking on **Continue** and you'll get redirected
on a page where your secret will be displayed in QRCode and Base32 formats.
You can use [Google Authenticator] to store it and get the generated tokens.
<p align="center">
<img src="../images/totp.png" width="400">
</p>
## Second factor with U2F security keys
**Authelia** also offers authentication using U2F (Universal 2-Factor) devices
like [Yubikey](Yubikey) USB security keys. U2F is one of the most secure
authentication protocol and is already available for Google, Facebook, Github
accounts and more.
Like TOTP, U2F requires you register your security key before authenticating.
To do so, click on the register button. This will send a link to the
user email address.
Confirm your identity by clicking on **Continue** and you'll be asked to
touch the token of your device to register. Upon successful registration,
you can authenticate using your U2F device by simply touching the token.
Easy, right?!
<p align="center">
<img src="./images/u2f.png" width="400">
</p>
## Password reset
With **Authelia**, you can also reset your password in no time. Click on the
**Forgot password?** link in the login page, provide the username of the user
requiring a password reset and **Authelia** will send an email a confirmation
email to the user email address.
Proceed with the password reset form and validate to reset your password.
<p align="center">
<img src="../images/reset_password.png" width="400">
</p>
## Access Control
With **Authelia**, you can define your own access control rules for finely
restricting user access to some resources and subdomains. Those rules are
defined and fully documented in the configuration file. They can apply to
users, groups or everyone.
Check out [config.template.yml] to see how they are defined.
## Single factor authentication
**Authelia** allows you to customize the authentication method to use for each
subdomain. The supported methods are either "single_factor" or "two_factor".
Please check [config.template.yml] to see an example of configuration.
It is also possible to use [basic authentication] to access a resource
protected by a single factor.
## Session management with Redis
When your users authenticate against Authelia, sessions are stored in a
Redis key/value store. You can specify your own Redis instance in
[config.template.yml].
[basic authentication]: https://en.wikipedia.org/wiki/Basic_access_authentication
[config.template.yml]: https://github.com/clems4ever/authelia/blob/master/config.template.yml
[Google Authenticator]: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en
[Yubikey]: https://www.yubico.com/products/yubikey-hardware/yubikey4/

View File

@ -0,0 +1,100 @@
# Getting started
**Authelia** can be tested in a matter of seconds with docker-compose based
on the latest image available on [Dockerhub] or by building the latest version
from the sources and use it in docker-compose.
## Pre-requisites
In order to test **Authelia**, we need to make sure that:
- **Docker** and **docker-compose** are installed.
- Some ports are open for listening on your machine.
- Some subdomains redirect to your machine to simulate the fact that some
applications you want to protect are served by some subdomains of
**example.com** on your machine.
### Docker & docker-compose
Make sure you have **docker** and **docker-compose** installed on your
machine.
Here are the versions used for testing in Travis:
docker --version
gave *Docker version 17.03.1-ce, build c6d412e*.
docker-compose --version
gave *docker-compose version 1.14.0, build c7bdf9e*.
### Available port
Make sure you don't have anything listening on port 8080 and 8085.
The port 8080 will be used by nginx to serve **Authelia** and the applications
we want to protect with **Authelia**.
The port 8085 is serving a webmail used to receive emails sent by **Authelia**
to validate your identity when registering U2F or TOTP secrets or when
resetting your password.
### Subdomain aliases
Make sure the following subdomains redirect to your machine by adding the
following lines to your **/etc/hosts**. It will alias the subdomains so that
nginx can redirect requests to the correct virtual host.
127.0.0.1 home.example.com
127.0.0.1 public.example.com
127.0.0.1 dev.example.com
127.0.0.1 admin.example.com
127.0.0.1 mx1.mail.example.com
127.0.0.1 mx2.mail.example.com
127.0.0.1 single_factor.example.com
127.0.0.1 login.example.com
## From Dockerhub
To deploy **Authelia** using the latest image from [Dockerhub], run the
following command:
./scripts/example-dockerhub/deploy-example.sh
## From source
To deploy **Authelia** from source, follow the [Build] manual and run the
following commands:
./scripts/example-commit/deploy-example.sh
## Test it!
After few seconds the services should be running and you should be able to
visit [https://home.example.com:8080/](https://home.example.com:8080/).
When accessing the login page, a self-signed certificate exception should
appear, it has to be trusted before you can get to the home page.
The certificate must also be trusted for each subdomain, therefore it is
normal to see this exception several times.
Below is what the login page looks like:
<p align="center">
<img src="../images/first_factor.png" width="400">
</p>
At some point, you'll be required to register a secret for setting up
the second factor. **Authelia** will send an email to the user email
address to confirm the user identity. In order to receive it, visit the
webmail at [http://localhost:8085](http://localhost:8085).
**Note:** If you cannot deploy the fake webmail for any reason. You can
configure **Authelia** to use the filesystem notifier (option available
in [config.template.yml]) that will send the content of the email in a
file instead of sending an email. It is advised to use this option
for testing only.
Enjoy!
[DockerHub]: https://hub.docker.com/r/clems4ever/authelia/
[Build]: ./docs/build.md

57
docs/security.md 100644
View File

@ -0,0 +1,57 @@
# Security
## Protection against cookie theft
Authelia uses two mechanism to protect against cookie theft:
1. session attribute `httpOnly` set to true make client-side code unable to
read the cookie.
2. session attribute `secure` ensure the cookie will never be sent over an
unsecure HTTP connections.
## Protection against multi-domain cookie attacks
Since Authelia uses multi-domain cookies to perform single sign-on, an
attacker who poisonned a user's DNS cache can easily retrieve the user's
cookies by making the user send a request to one of the attacker's IPs.
To mitigate this risk, it's advisable to only use HTTPS connections with valid
certificates and enforce it with HTTP Strict Transport Security ([HSTS]) so
that the attacker must also require the certificate to retrieve the cookies.
Note that using [HSTS] has consequences. That's why you should read the blog
post nginx has written on [HSTS].
## More protections measures with Nginx
You can also apply the following headers to your nginx configuration for
improving security. Please read the documentation of those headers before
applying them blindly.
```
# We don't want any credentials / TOTP secret key / QR code to be cached by
# the client
add_header Cache-Control "no-store";
add_header Pragma "no-cache";
# Clickjacking / XSS protection
# We don't want Authelia's login page to be rendered within a <frame>,
# <iframe> or <object> from an external website.
add_header X-Frame-Options "SAMEORIGIN";
# Block pages from loading when they detect reflected XSS attacks.
add_header X-XSS-Protection "1; mode=block";
```
## Helmet
To improve even more the security, [Helmet] has been added to **Authelia**.
## Contributing
If you find possible vulnerabilities or threats, do not hesitate to contribute
either by writing a test case demonstrating the possible attack and if
possible some solutions to prevent it or submit a PR.
[HSTS]: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
[Helmet]: https://helmetjs.github.io/

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 38 KiB