mirror of
https://github.com/Tivix/django-rest-auth.git
synced 2024-11-22 09:06:40 +03:00
Merge branch 'pr/1'
This commit is contained in:
commit
759f59f1ab
47
.circleci/config.yml
Normal file
47
.circleci/config.yml
Normal file
|
@ -0,0 +1,47 @@
|
|||
version: 2.1
|
||||
orbs:
|
||||
docker: circleci/docker@0.6.0
|
||||
|
||||
jobs:
|
||||
test-django-3: &template
|
||||
docker:
|
||||
- image: circleci/python:3.8.0
|
||||
environment:
|
||||
DJANGO_VERSION: 3.1
|
||||
DRF: 3.11.1
|
||||
executor: docker/docker
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: pip install --user Django==$DJANGO_VERSION djangorestframework==$DRF
|
||||
name: "Pip install version-specific Django + DRF"
|
||||
- run:
|
||||
command: pip install --user -r dj_rest_auth/tests/requirements.pip
|
||||
name: "Pip Install test requirements"
|
||||
- run:
|
||||
command: |
|
||||
mkdir -p test-results/
|
||||
coverage run --source=dj_rest_auth setup.py test
|
||||
coverage report
|
||||
name: Test
|
||||
- run:
|
||||
command: COVERALLS_REPO_TOKEN=Q58WdUuZOi89XHyDeDsGE2lxUGQ2IfqP3 coveralls
|
||||
name: Coverage
|
||||
- run:
|
||||
command: python3 setup.py sdist
|
||||
name: Build
|
||||
- store_test_results:
|
||||
path: test-results/
|
||||
- store_artifacts:
|
||||
path: dist/
|
||||
test-django-2:
|
||||
<<: *template
|
||||
environment:
|
||||
DJANGO_VERSION: 2.2.10
|
||||
DRF: 3.9
|
||||
|
||||
workflows:
|
||||
main:
|
||||
jobs:
|
||||
- test-django-3
|
||||
- test-django-2
|
2
.coveralls.yml
Normal file
2
.coveralls.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
service_name: travis-pro
|
||||
repo_token: Q58WdUuZOi89XHyDeDsGE2lxUGQ2IfqP3
|
14
.github/workflows/main.yml
vendored
Normal file
14
.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Release to PyPi
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Publish package
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.pypi_password }}
|
84
.gitignore
vendored
84
.gitignore
vendored
|
@ -1,26 +1,35 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
bin/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# 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
|
||||
|
@ -30,28 +39,75 @@ pip-delete-this-directory.txt
|
|||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
coverage_html
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
test-results/
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Rope
|
||||
.ropeproject
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
*.pot
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
.DS_Store
|
||||
db.sqlite3
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# IntelliJ IDE files
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
demo/react-spa/node_modules/
|
||||
demo/react-spa/yarn.lock
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
|
|
23
.travis.yml
23
.travis.yml
|
@ -1,23 +0,0 @@
|
|||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
env:
|
||||
- DJANGO=1.8.* DRF=3.6.*
|
||||
- DJANGO=1.11.* DRF=3.7.*
|
||||
- DJANGO=2.0.* DRF=3.7.*
|
||||
install:
|
||||
- pip install -q Django==$DJANGO djangorestframework==$DRF
|
||||
- pip install coveralls
|
||||
- pip install -r rest_auth/tests/requirements.pip
|
||||
script:
|
||||
- coverage run --source=rest_auth setup.py test
|
||||
after_success:
|
||||
- coveralls
|
||||
before_script:
|
||||
- flake8 . --config=flake8
|
||||
matrix:
|
||||
exclude:
|
||||
- python: "2.7"
|
||||
env: DJANGO=2.0.* DRF=3.7.*
|
2
AUTHORS
2
AUTHORS
|
@ -1 +1 @@
|
|||
http://github.com/Tivix/django-rest-auth/contributors
|
||||
http://github.com/jazzband/dj-rest-auth/contributors
|
||||
|
|
3
CONTRIBUTING.md
Normal file
3
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/)
|
||||
|
||||
This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/about/conduct) and follow the [guidelines](https://jazzband.co/about/guidelines).
|
4
LICENSE
4
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Tivix
|
||||
Copyright (c) 2014 iMerica https://github.com/iMerica/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
|
|
@ -2,4 +2,4 @@ include AUTHORS
|
|||
include LICENSE
|
||||
include MANIFEST.in
|
||||
include README.md
|
||||
graft rest_auth
|
||||
graft dj_rest_auth
|
||||
|
|
77
README.md
Normal file
77
README.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Dj-Rest-Auth
|
||||
[![<iMerica>](https://circleci.com/gh/jazzband/dj-rest-auth.svg?style=svg)](https://app.circleci.com/pipelines/github/jazzband/dj-rest-auth)
|
||||
[![Jazzband](https://jazzband.co/static/img/badge.svg)](https://jazzband.co/)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/jazzband/dj-rest-auth/badge.svg?branch=master)](https://coveralls.io/github/jazzband/dj-rest-auth?branch=master)
|
||||
|
||||
Drop-in API endpoints for handling authentication securely in Django Rest Framework. Works especially well
|
||||
with SPAs (e.g React, Vue, Angular), and Mobile applications.
|
||||
|
||||
## Requirements
|
||||
- Django 2 or 3.
|
||||
- Python 3
|
||||
|
||||
## Quick Setup
|
||||
|
||||
Install package
|
||||
|
||||
pip install dj-rest-auth
|
||||
|
||||
Add `dj_rest_auth` app to INSTALLED_APPS in your django settings.py:
|
||||
|
||||
```python
|
||||
INSTALLED_APPS = (
|
||||
...,
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
...,
|
||||
'dj_rest_auth'
|
||||
)
|
||||
```
|
||||
|
||||
Add URL patterns
|
||||
|
||||
```python
|
||||
urlpatterns = [
|
||||
url(r'^dj-rest-auth/', include('dj_rest_auth.urls'))
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
(Optional) Use Http-Only cookies
|
||||
|
||||
```python
|
||||
REST_USE_JWT = True
|
||||
JWT_AUTH_COOKIE = 'jwt-auth'
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
To run the tests within a virtualenv, run `python runtests.py` from the repository directory.
|
||||
The easiest way to run test coverage is with [`coverage`](https://pypi.org/project/coverage/),
|
||||
which runs the tests against all supported Django installs. To run the test coverage
|
||||
within a virtualenv, run `coverage run ./runtests.py` from the repository directory then run `coverage report`.
|
||||
|
||||
#### Tox
|
||||
|
||||
Testing may also be done using [`tox`](https://pypi.org/project/tox/), which
|
||||
will run the tests against all supported combinations of python and django.
|
||||
|
||||
Install tox, either globally or within a virtualenv, and then simply run `tox`
|
||||
from the repository directory. As there are many combinations, you may run them
|
||||
in [`parallel`](https://tox.readthedocs.io/en/latest/config.html#cmdoption-tox-p)
|
||||
using `tox --parallel`.
|
||||
|
||||
The `tox.ini` includes an environment for testing code [`coverage`](https://pypi.org/project/coverage/)
|
||||
and you can run it and view this report with `tox -e coverage`.
|
||||
|
||||
Linting may also be performed via [`flake8`](https://pypi.org/project/flake8/)
|
||||
by running `tox -e flake8`.
|
||||
|
||||
### Documentation
|
||||
|
||||
View the full documentation here: https://dj-rest-auth.readthedocs.io/en/latest/index.html
|
||||
|
||||
|
||||
### Acknowledgements
|
||||
|
||||
This project began as a fork of `django-rest-auth`. Big thanks to everyone who contributed to that repo!
|
31
README.rst
31
README.rst
|
@ -1,31 +0,0 @@
|
|||
Welcome to django-rest-auth
|
||||
===========================
|
||||
|
||||
.. image:: https://travis-ci.org/Tivix/django-rest-auth.svg
|
||||
:target: https://travis-ci.org/Tivix/django-rest-auth
|
||||
|
||||
|
||||
.. image:: https://coveralls.io/repos/Tivix/django-rest-auth/badge.svg
|
||||
:target: https://coveralls.io/r/Tivix/django-rest-auth?branch=master
|
||||
|
||||
|
||||
.. image:: https://readthedocs.org/projects/django-rest-auth/badge/?version=latest
|
||||
:target: https://readthedocs.org/projects/django-rest-auth/?badge=latest
|
||||
|
||||
|
||||
Django-rest-auth provides a set of REST API endpoints for Authentication and Registration
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
http://django-rest-auth.readthedocs.org/en/latest/
|
||||
|
||||
|
||||
Source code
|
||||
-----------
|
||||
https://github.com/Tivix/django-rest-auth
|
||||
|
||||
|
||||
Stack Overflow
|
||||
-----------
|
||||
http://stackoverflow.com/questions/tagged/django-rest-auth
|
|
@ -31,23 +31,23 @@ INSTALLED_APPS = (
|
|||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
# 'django.contrib.messages',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'rest_auth',
|
||||
|
||||
'dj_rest_auth',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'rest_auth.registration',
|
||||
'dj_rest_auth.registration',
|
||||
'allauth.socialaccount',
|
||||
'allauth.socialaccount.providers.facebook',
|
||||
'rest_framework_swagger',
|
||||
'corsheaders'
|
||||
)
|
||||
|
||||
MIDDLEWARE = (
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
|
@ -116,14 +116,23 @@ ACCOUNT_EMAIL_REQUIRED = False
|
|||
ACCOUNT_AUTHENTICATION_METHOD = 'username'
|
||||
ACCOUNT_EMAIL_VERIFICATION = 'optional'
|
||||
|
||||
REST_USE_JWT = True
|
||||
JWT_AUTH_COOKIE = 'auth'
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
)
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||
),
|
||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
|
||||
}
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
'LOGIN_URL': 'login',
|
||||
'LOGOUT_URL': 'logout',
|
||||
}
|
||||
|
||||
|
||||
# For demo purposes only. Use a white list in the real world.
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
from django.views.generic import TemplateView, RedirectView
|
||||
|
||||
from django.views.generic import RedirectView, TemplateView
|
||||
from rest_framework_swagger.views import get_swagger_view
|
||||
|
||||
urlpatterns = [
|
||||
|
@ -35,8 +34,8 @@ urlpatterns = [
|
|||
TemplateView.as_view(template_name="password_reset_confirm.html"),
|
||||
name='password_reset_confirm'),
|
||||
|
||||
url(r'^rest-auth/', include('rest_auth.urls')),
|
||||
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
|
||||
url(r'^dj-rest-auth/', include('dj_rest_auth.urls')),
|
||||
url(r'^dj-rest-auth/registration/', include('dj_rest_auth.registration.urls')),
|
||||
url(r'^account/', include('allauth.urls')),
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(r'^accounts/profile/$', RedirectView.as_view(url='/', permanent=True), name='profile-redirect'),
|
||||
|
|
|
@ -8,7 +8,9 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
|||
"""
|
||||
|
||||
import os
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
|
23
demo/react-spa/.gitignore
vendored
Normal file
23
demo/react-spa/.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
68
demo/react-spa/README.md
Normal file
68
demo/react-spa/README.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn start`
|
||||
|
||||
Runs the app in the development mode.<br />
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br />
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br />
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `yarn build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br />
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br />
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `yarn eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
|
||||
|
||||
### `yarn build` fails to minify
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
|
34
demo/react-spa/package.json
Normal file
34
demo/react-spa/package.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "react-spa",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-scripts": "3.4.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
BIN
demo/react-spa/public/favicon.ico
Normal file
BIN
demo/react-spa/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
43
demo/react-spa/public/index.html
Normal file
43
demo/react-spa/public/index.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
BIN
demo/react-spa/public/logo192.png
Normal file
BIN
demo/react-spa/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
demo/react-spa/public/logo512.png
Normal file
BIN
demo/react-spa/public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
25
demo/react-spa/public/manifest.json
Normal file
25
demo/react-spa/public/manifest.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
demo/react-spa/public/robots.txt
Normal file
3
demo/react-spa/public/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
40
demo/react-spa/src/App.css
Normal file
40
demo/react-spa/src/App.css
Normal file
|
@ -0,0 +1,40 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
66
demo/react-spa/src/App.js
vendored
Normal file
66
demo/react-spa/src/App.js
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
import React, { useState } from 'react';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
const [ resp, changeResponse ] = useState(null);
|
||||
const [ username, changeUsername ] = useState('');
|
||||
const [ password, changePassword ] = useState('');
|
||||
|
||||
function onSubmit(e) {
|
||||
e.preventDefault();
|
||||
return fetch('http://localhost:8000/dj-rest-auth/login/', {
|
||||
method: 'POST',
|
||||
credentials: 'omit',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({username, password})
|
||||
}).then(resp => resp.json()).then(data => {
|
||||
changeResponse(data)
|
||||
}).catch(error => console.log('error ->', error))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<h1>
|
||||
Login
|
||||
</h1>
|
||||
<div className={'help-text'}>
|
||||
Inspect the network requests in your browser to view headers returned by dj-rest-auth.
|
||||
</div>
|
||||
<div>
|
||||
{resp &&
|
||||
<div className={'response'}>
|
||||
<code>
|
||||
{JSON.stringify(resp)}
|
||||
</code>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<form onSubmit={onSubmit}>
|
||||
<div>
|
||||
<input
|
||||
onChange={(e) => changeUsername(e.target.value)}
|
||||
value={username}
|
||||
type={'input'}
|
||||
name={'username'}/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
onChange={(e) => changePassword(e.target.value)}
|
||||
value={password}
|
||||
type={'password'}
|
||||
name={'password'}/>
|
||||
</div>
|
||||
<button type={'submit'}>Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
9
demo/react-spa/src/App.test.js
Normal file
9
demo/react-spa/src/App.test.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
const { getByText } = render(<App />);
|
||||
const linkElement = getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
48
demo/react-spa/src/index.css
Normal file
48
demo/react-spa/src/index.css
Normal file
|
@ -0,0 +1,48 @@
|
|||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body div {
|
||||
flex-direction: row;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
ul {
|
||||
width: 240px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
word-break: break-all;
|
||||
height: 200px;
|
||||
color: #0f0f0f;
|
||||
}
|
||||
|
||||
.response {
|
||||
margin: 20px;
|
||||
background-color: #eee;
|
||||
width: 80%;
|
||||
height: 200px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 11px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
form > div {
|
||||
margin: 20px;
|
||||
}
|
17
demo/react-spa/src/index.js
vendored
Normal file
17
demo/react-spa/src/index.js
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
7
demo/react-spa/src/logo.svg
Normal file
7
demo/react-spa/src/logo.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
141
demo/react-spa/src/serviceWorker.js
vendored
Normal file
141
demo/react-spa/src/serviceWorker.js
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { 'Service-Worker': 'script' },
|
||||
})
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready
|
||||
.then(registration => {
|
||||
registration.unregister();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error.message);
|
||||
});
|
||||
}
|
||||
}
|
5
demo/react-spa/src/setupTests.js
vendored
Normal file
5
demo/react-spa/src/setupTests.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
|
@ -1,6 +1,8 @@
|
|||
django>=1.9.0
|
||||
django-rest-auth==0.9.3
|
||||
djangorestframework>=3.7.0
|
||||
django>=2.2
|
||||
dj-rest-auth @ git+https://github.com/jazzband/dj-rest-auth.git@master
|
||||
djangorestframework>=3.11.0
|
||||
djangorestframework-simplejwt==4.4.0
|
||||
django-allauth>=0.24.1
|
||||
six==1.9.0
|
||||
django-rest-swagger==2.0.7
|
||||
django-cors-headers==3.2.1
|
||||
coreapi==2.3.3
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="Django-rest-auth demo">
|
||||
<meta name="author" content="Tivix, Inc.">
|
||||
<meta name="description" content="Django-dj-rest-auth demo">
|
||||
<meta name="author" content="iMerica, Inc.">
|
||||
|
||||
<title>django-rest-auth demo</title>
|
||||
<title>dj-rest-auth demo</title>
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
|
||||
|
@ -53,13 +53,13 @@
|
|||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">django-rest-auth demo</a>
|
||||
<a class="navbar-brand" href="/">dj-rest-auth demo</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="/">Demo</a></li>
|
||||
<li><a target="_blank" href="http://django-rest-auth.readthedocs.org/en/latest/">Documentation</a></li>
|
||||
<li><a target="_blank" href="https://github.com/Tivix/django-rest-auth">Source code</a></li>
|
||||
<li><a target="_blank" href="http://dj-rest-auth.readthedocs.org/en/latest/">Documentation</a></li>
|
||||
<li><a target="_blank" href="https://github.com/jazzband/dj-rest-auth">Source code</a></li>
|
||||
<li><a target="_blank" href="{% url 'api_docs' %}">API Docs</a></li>
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block content %}
|
||||
<!-- Main jumbotron for a primary marketing message or call to action -->
|
||||
<div class="jumbotron">
|
||||
<h1>django-rest-auth demo</h1>
|
||||
<p>Welcome in django-rest-auth demo project!</p>
|
||||
<h1>dj-rest-auth demo</h1>
|
||||
<p>Welcome in dj-rest-auth demo project!</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
52
demo/templates/rest_framework/api.html
Normal file
52
demo/templates/rest_framework/api.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
{% extends "rest_framework/base.html" %}
|
||||
|
||||
{% block style %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
#btn-link {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
display: block;
|
||||
padding: 3px 20px;
|
||||
clear: both;
|
||||
font-weight: 400;
|
||||
line-height: 1.42857143;
|
||||
color: #A30000;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
#btn-link:hover {
|
||||
background: #EEEEEE;
|
||||
color: #C20000;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block userlinks %}
|
||||
{% if user.is_authenticated or response.data.access_token %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
{% firstof user.username 'Registered' %}
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
{% url 'rest_user_details' as user_url %}
|
||||
<li><a href="{{ user_url }}">User</a></li>
|
||||
<li>
|
||||
{% url 'rest_logout' as logout_url %}
|
||||
<form action="{{ logout_url }}" method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" id="btn-link">Logout</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
{% url 'rest_login' as login_url %}
|
||||
<li><a href="{{ login_url }}">Login</a></li>
|
||||
{% url 'rest_register' as register_url %}
|
||||
<li><a href="{{ register_url }}">Register</a></li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,3 +0,0 @@
|
|||
--editable .
|
||||
responses>=0.5.0
|
||||
djangorestframework-jwt
|
8
dj_rest_auth/__version__.py
Normal file
8
dj_rest_auth/__version__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
__title__ = 'dj-rest-auth'
|
||||
__description__ = 'Authentication and Registration in Django Rest Framework.'
|
||||
__url__ = 'http://github.com/jazzband/dj-rest-auth'
|
||||
__version__ = '1.1.1'
|
||||
__author__ = '@iMerica https://github.com/iMerica'
|
||||
__author_email__ = 'imichael@pm.me'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright 2020 @iMerica https://github.com/iMerica'
|
40
dj_rest_auth/app_settings.py
Normal file
40
dj_rest_auth/app_settings.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from dj_rest_auth.serializers import JWTSerializer as DefaultJWTSerializer
|
||||
from dj_rest_auth.serializers import LoginSerializer as DefaultLoginSerializer
|
||||
from dj_rest_auth.serializers import \
|
||||
PasswordChangeSerializer as DefaultPasswordChangeSerializer
|
||||
from dj_rest_auth.serializers import \
|
||||
PasswordResetConfirmSerializer as DefaultPasswordResetConfirmSerializer
|
||||
from dj_rest_auth.serializers import \
|
||||
PasswordResetSerializer as DefaultPasswordResetSerializer
|
||||
from dj_rest_auth.serializers import TokenSerializer as DefaultTokenSerializer
|
||||
from dj_rest_auth.serializers import \
|
||||
UserDetailsSerializer as DefaultUserDetailsSerializer
|
||||
from django.conf import settings
|
||||
|
||||
from .utils import default_create_token, import_callable
|
||||
|
||||
create_token = import_callable(getattr(settings, 'REST_AUTH_TOKEN_CREATOR', default_create_token))
|
||||
|
||||
serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {})
|
||||
|
||||
TokenSerializer = import_callable(serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer))
|
||||
|
||||
JWTSerializer = import_callable(serializers.get('JWT_SERIALIZER', DefaultJWTSerializer))
|
||||
|
||||
UserDetailsSerializer = import_callable(serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer))
|
||||
|
||||
LoginSerializer = import_callable(serializers.get('LOGIN_SERIALIZER', DefaultLoginSerializer))
|
||||
|
||||
PasswordResetSerializer = import_callable(serializers.get(
|
||||
'PASSWORD_RESET_SERIALIZER', DefaultPasswordResetSerializer
|
||||
))
|
||||
|
||||
PasswordResetConfirmSerializer = import_callable(serializers.get(
|
||||
'PASSWORD_RESET_CONFIRM_SERIALIZER', DefaultPasswordResetConfirmSerializer
|
||||
))
|
||||
|
||||
PasswordChangeSerializer = import_callable(
|
||||
serializers.get('PASSWORD_CHANGE_SERIALIZER', DefaultPasswordChangeSerializer)
|
||||
)
|
||||
|
||||
JWT_AUTH_COOKIE = getattr(settings, 'JWT_AUTH_COOKIE', None)
|
43
dj_rest_auth/jwt_auth.py
Normal file
43
dj_rest_auth/jwt_auth.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from django.conf import settings
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.authentication import CSRFCheck
|
||||
|
||||
class JWTCookieAuthentication(JWTAuthentication):
|
||||
"""
|
||||
An authentication plugin that hopefully authenticates requests through a JSON web
|
||||
token provided in a request cookie (and through the header as normal, with a
|
||||
preference to the header).
|
||||
"""
|
||||
def enforce_csrf(self, request):
|
||||
"""
|
||||
Enforce CSRF validation for session based authentication.
|
||||
"""
|
||||
check = CSRFCheck()
|
||||
# populates request.META['CSRF_COOKIE'], which is used in process_view()
|
||||
check.process_request(request)
|
||||
reason = check.process_view(request, None, (), {})
|
||||
if reason:
|
||||
# CSRF failed, bail with explicit error message
|
||||
raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
|
||||
|
||||
def authenticate(self, request):
|
||||
cookie_name = getattr(settings, 'JWT_AUTH_COOKIE', None)
|
||||
header = self.get_header(request)
|
||||
if header is None:
|
||||
if cookie_name:
|
||||
raw_token = request.COOKIES.get(cookie_name)
|
||||
if getattr(settings, 'JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED', False): #True at your own risk
|
||||
self.enforce_csrf(request)
|
||||
elif raw_token is not None and getattr(settings, 'JWT_AUTH_COOKIE_USE_CSRF', False):
|
||||
self.enforce_csrf(request)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
raw_token = self.get_raw_token(header)
|
||||
|
||||
if raw_token is None:
|
||||
return None
|
||||
|
||||
validated_token = self.get_validated_token(raw_token)
|
||||
return self.get_user(validated_token), validated_token
|
BIN
dj_rest_auth/locale/cs/LC_MESSAGES/django.mo
Normal file
BIN
dj_rest_auth/locale/cs/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
102
dj_rest_auth/locale/cs/LC_MESSAGES/django.po
Normal file
102
dj_rest_auth/locale/cs/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,102 @@
|
|||
# Czech translations of jazzband/dj-rest-auth
|
||||
#
|
||||
# This file is distributed under the same license as the jazzband/dj-rest-auth package.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: jazzband/dj-rest-auth\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/jazzband/dj-rest-auth/issues\n"
|
||||
"POT-Creation-Date: 2018-06-27 23:05+0200\n"
|
||||
"PO-Revision-Date: 2018-06-27 23:22+0200\n"
|
||||
"Language: cs\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||
"Last-Translator: Václav Dohnal <vaclav.dohnal@gmail.com>\n"
|
||||
"Language-Team: N/A\n"
|
||||
"X-Generator: Poedit 2.0.8\n"
|
||||
|
||||
#: .\registration\serializers.py:67
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr "View není definováno, předejte jej jako proměnnou kontextu"
|
||||
|
||||
#: .\registration\serializers.py:72
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr "Definujte adapter_class ve view"
|
||||
|
||||
#: .\registration\serializers.py:91
|
||||
msgid "Define callback_url in view"
|
||||
msgstr "Definujte callback_url ve view"
|
||||
|
||||
#: .\registration\serializers.py:95
|
||||
msgid "Define client_class in view"
|
||||
msgstr "Definujte client_class ve view"
|
||||
|
||||
#: .\registration\serializers.py:116
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr "Nesprávný vstup. access_token je povinný."
|
||||
|
||||
#: .\registration\serializers.py:125
|
||||
msgid "Incorrect value"
|
||||
msgstr "Nesprávná hodnota"
|
||||
|
||||
#: .\registration\serializers.py:139
|
||||
msgid "User is already registered with this e-mail address."
|
||||
msgstr "Uživatel s touto adresou je již registrován."
|
||||
|
||||
#: .\registration\serializers.py:185
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr "Uživatel s touto adresou je již registrován."
|
||||
|
||||
#: .\registration\serializers.py:193
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "Zadaná hesla se neshodují."
|
||||
|
||||
#: .\registration\views.py:51
|
||||
msgid "Verification e-mail sent."
|
||||
msgstr "Ověřovací e-mail odeslán."
|
||||
|
||||
#: .\registration\views.py:98
|
||||
msgid "ok"
|
||||
msgstr "ok"
|
||||
|
||||
#: .\serializers.py:30
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr "Musí obsahovat \"e-mail\" a \"heslo\"."
|
||||
|
||||
#: .\serializers.py:41
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "Musí obsahovat \"uživatelské jméno\" a \"heslo\"."
|
||||
|
||||
#: .\serializers.py:54
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr "Musí obsahovat \"uživatelské jméno\" nebo \"e-mail\" a \"heslo\"."
|
||||
|
||||
#: .\serializers.py:95
|
||||
msgid "User account is disabled."
|
||||
msgstr "Uživatelský účet je zakázán."
|
||||
|
||||
#: .\serializers.py:98
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "Pomocí zadaných údajů se nelze přihlásit."
|
||||
|
||||
#: .\serializers.py:107
|
||||
msgid "E-mail is not verified."
|
||||
msgstr "E-mail není ověřený."
|
||||
|
||||
#: .\views.py:127
|
||||
msgid "Successfully logged out."
|
||||
msgstr "Byli jste úspěšně odhlášeni."
|
||||
|
||||
#: .\views.py:175
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr "E-mail pro resetování hesla byl odeslán."
|
||||
|
||||
#: .\views.py:201
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr "Vaše heslo bylo resetováno."
|
||||
|
||||
#: .\views.py:223
|
||||
msgid "New password has been saved."
|
||||
msgstr "Nové heslo bylo uloženo."
|
BIN
dj_rest_auth/locale/fr/LC_MESSAGES/django.mo
Normal file
BIN
dj_rest_auth/locale/fr/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
98
dj_rest_auth/locale/fr/LC_MESSAGES/django.po
Normal file
98
dj_rest_auth/locale/fr/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,98 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-12-22 11:37-0800\n"
|
||||
"PO-Revision-Date: 2017-02-14 13:27+0100\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 1.8.11\n"
|
||||
|
||||
#: registration/serializers.py:53
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr "La “View” n’est pas définie, passez la en variable contextuelle"
|
||||
|
||||
#: registration/serializers.py:58
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr "Définissez “adapter_class” dans la vue"
|
||||
|
||||
#: registration/serializers.py:77
|
||||
msgid "Define callback_url in view"
|
||||
msgstr "Définissez “callback_url” dans la vue"
|
||||
|
||||
#: registration/serializers.py:81
|
||||
msgid "Define client_class in view"
|
||||
msgstr "Définissez “client_class” dans la vue"
|
||||
|
||||
#: registration/serializers.py:102
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr "Paramètres incorrects. Il faut “access_token” ou “code”."
|
||||
|
||||
#: registration/serializers.py:111
|
||||
msgid "Incorrect value"
|
||||
msgstr "Paramètre incorrect"
|
||||
|
||||
#: registration/serializers.py:140
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr "Un utilisateur existe déjà avec cette adresse email."
|
||||
|
||||
#: registration/serializers.py:148
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "Les deux mots de passes ne sont pas les mêmes."
|
||||
|
||||
#: registration/views.py:82
|
||||
msgid "ok"
|
||||
msgstr "Ok"
|
||||
|
||||
#: serializers.py:30
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr "Doit inclure “email” et “password”."
|
||||
|
||||
#: serializers.py:41
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "Doit inclure “username” et “password”."
|
||||
|
||||
#: serializers.py:54
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr "Doit inclure un “username” ou “email”, et un “password”."
|
||||
|
||||
#: serializers.py:95
|
||||
msgid "User account is disabled."
|
||||
msgstr "Le compte utilisateur est désactivé."
|
||||
|
||||
#: serializers.py:98
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "Connexion impossible avec les informations fournies."
|
||||
|
||||
#: serializers.py:107
|
||||
msgid "E-mail is not verified."
|
||||
msgstr "L’adresse email n’a pas été vérifiée."
|
||||
|
||||
#: views.py:114
|
||||
msgid "Successfully logged out."
|
||||
msgstr "Déconnexion effectuée avec succès."
|
||||
|
||||
#: views.py:162
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr "L’email de réinitialisation du mot de passe a été envoyé."
|
||||
|
||||
#: views.py:184
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr "Le mot de passe a été réinitialisé."
|
||||
|
||||
#: views.py:202
|
||||
msgid "New password has been saved."
|
||||
msgstr "Le nouveau mot de passe est sauvé."
|
||||
|
||||
#~ msgid "Error"
|
||||
#~ msgstr "Fehler"
|
BIN
dj_rest_auth/locale/it/LC_MESSAGES/django.mo
Normal file
BIN
dj_rest_auth/locale/it/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
123
dj_rest_auth/locale/it/LC_MESSAGES/django.po
Normal file
123
dj_rest_auth/locale/it/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,123 @@
|
|||
# Italian language.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-05-07 09:46+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Francesco Pinzauti <info@pinzauti.tech>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\registration\serializers.py:67
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr "\"view\" non è definito, passalo come variabile di contesto"
|
||||
|
||||
#: .\registration\serializers.py:72
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr "Definisci \"adapter_class\" in view"
|
||||
|
||||
#: .\registration\serializers.py:91
|
||||
msgid "Define callback_url in view"
|
||||
msgstr "Definisci \"callback_url\" in view"
|
||||
|
||||
#: .\registration\serializers.py:95
|
||||
msgid "Define client_class in view"
|
||||
msgstr "Definisci \"client_class\" in view"
|
||||
|
||||
#: .\registration\serializers.py:116
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr "Input errato. È richiesto \"access_token\" o \"code\"."
|
||||
|
||||
#: .\registration\serializers.py:125
|
||||
msgid "Incorrect value"
|
||||
msgstr "Valore errato"
|
||||
|
||||
#: .\registration\serializers.py:139
|
||||
msgid "User is already registered with this e-mail address."
|
||||
msgstr "Un altro utente è già registrato con questo indirizzo e-mail."
|
||||
|
||||
#: .\registration\serializers.py:185
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr "Un altro utente è già registrato con questo indirizzo e-mail."
|
||||
|
||||
#: .\registration\serializers.py:193
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "Le password non corrispondono."
|
||||
|
||||
#: .\registration\views.py:47
|
||||
msgid "Verification e-mail sent."
|
||||
msgstr "E-mail di verifica inviata."
|
||||
|
||||
#: .\registration\views.py:95
|
||||
msgid "ok"
|
||||
msgstr "ok"
|
||||
|
||||
#: .\serializers.py:32
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr "Deve includere \"email\" e \"password\"."
|
||||
|
||||
#: .\serializers.py:43
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "Deve includere \"email\" e \"password\"."
|
||||
|
||||
#: .\serializers.py:56
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr "Deve includere o \"username\" o \"email\" e \"password\"."
|
||||
|
||||
#: .\serializers.py:97
|
||||
msgid "User account is disabled."
|
||||
msgstr "L'account è disabilitato."
|
||||
|
||||
#: .\serializers.py:100
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "Impossibile accedere con le credenziali fornite."
|
||||
|
||||
#: .\serializers.py:109
|
||||
msgid "E-mail is not verified."
|
||||
msgstr "L'e-mail non è verificata."
|
||||
|
||||
#: .\serializers.py:264
|
||||
msgid "Your old password was entered incorrectly. Please enter it again."
|
||||
msgstr "La tua password precedente non è corretta. Inseriscila nuovamente."
|
||||
|
||||
#: .\views.py:139
|
||||
msgid "Successfully logged out."
|
||||
msgstr "Disconnesso con successo."
|
||||
|
||||
#: .\views.py:161
|
||||
msgid "Refresh token was not included in request data."
|
||||
msgstr "Il \"Refresh token\" non è presente nei dati della richiesta."
|
||||
|
||||
#: .\views.py:171 .\views.py:175
|
||||
msgid "An error has occurred."
|
||||
msgstr "Si è verificato un errore."
|
||||
|
||||
#: .\views.py:180
|
||||
msgid ""
|
||||
"Neither cookies or blacklist are enabled, so the token has not been deleted "
|
||||
"server side. Please make sure the token is deleted client side."
|
||||
msgstr ""
|
||||
"Né i cookies né la blacklist sono abilitati, quindi il token non è stato eliminato "
|
||||
"lato server. Assicurarsi che il token sia eliminato lato client."
|
||||
|
||||
#: .\views.py:230
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr "L'e-mail per il recupero della password è stata inviata."
|
||||
|
||||
#: .\views.py:256
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr "Password aggiornata."
|
||||
|
||||
#: .\views.py:278
|
||||
msgid "New password has been saved."
|
||||
msgstr "Nuova password salvata."
|
BIN
dj_rest_auth/locale/ko/LC_MESSAGES/django.mo
Normal file
BIN
dj_rest_auth/locale/ko/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
99
dj_rest_auth/locale/ko/LC_MESSAGES/django.po
Normal file
99
dj_rest_auth/locale/ko/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,99 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-03-05 21:56-0800\n"
|
||||
"PO-Revision-Date: 2018-03-20 17:52+0900\n"
|
||||
"Last-Translator: Jeonsgoo Park <toracle@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: ko\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: registration/serializers.py:53
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr "View가 정의되지 않았습니다. 컨텍스트 변수에 포함해주세요"
|
||||
|
||||
#: registration/serializers.py:58
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr "view에 adapter_class를 정의하세요"
|
||||
|
||||
#: registration/serializers.py:77
|
||||
msgid "Define callback_url in view"
|
||||
msgstr "view에 callback_url을 정의하세요"
|
||||
|
||||
#: registration/serializers.py:81
|
||||
msgid "Define client_class in view"
|
||||
msgstr "view에 client_class를 정의하세요"
|
||||
|
||||
#: registration/serializers.py:102
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr "올바르지 않은 입력입니다. access_token이나 code가 필요합니다."
|
||||
|
||||
#: registration/serializers.py:111
|
||||
msgid "Incorrect value"
|
||||
msgstr "올바르지 않은 값"
|
||||
|
||||
#: registration/serializers.py:140
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr "이미 이 이메일 주소로 등록된 사용자가 있습니다."
|
||||
|
||||
#: registration/serializers.py:148
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "두 개의 패스워드 필드가 서로 맞지 않습니다."
|
||||
|
||||
#: registration/views.py:44
|
||||
msgid "Verification e-mail sent."
|
||||
msgstr "확인 이메일을 발송했습니다."
|
||||
|
||||
#: registration/views.py:91
|
||||
msgid "ok"
|
||||
msgstr "ok"
|
||||
|
||||
#: serializers.py:30
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr "\"email\"과 \"password\"를 반드시 포함해야 합니다."
|
||||
|
||||
#: serializers.py:41
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "\"username\"과 \"password\"를 반드시 포함해야 합니다."
|
||||
|
||||
#: serializers.py:54
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr "\"username\"이나 \"email\", 그리고 \"password\"를 반드시 포함해야 합니다."
|
||||
|
||||
#: serializers.py:95
|
||||
msgid "User account is disabled."
|
||||
msgstr "사용자 계정이 비활성화 되어있습니다."
|
||||
|
||||
#: serializers.py:98
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "주어진 자격 증명으로 로그인이 불가능합니다."
|
||||
|
||||
#: serializers.py:107
|
||||
msgid "E-mail is not verified."
|
||||
msgstr "이메일 주소가 확인되지 않았습니다."
|
||||
|
||||
#: views.py:126
|
||||
msgid "Successfully logged out."
|
||||
msgstr "로그아웃되었습니다."
|
||||
|
||||
#: views.py:174
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr "패스워드 초기화 이메일이 발송되었습니다."
|
||||
|
||||
#: views.py:200
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr "새로운 패스워드로 패스워드가 초기화 되었습니다."
|
||||
|
||||
#: views.py:222
|
||||
msgid "New password has been saved."
|
||||
msgstr "새로운 패스워드가 저장되었습니다."
|
BIN
dj_rest_auth/locale/pt_BR/LC_MESSAGES/django.mo
Normal file
BIN
dj_rest_auth/locale/pt_BR/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
99
dj_rest_auth/locale/pt_BR/LC_MESSAGES/django.po
Normal file
99
dj_rest_auth/locale/pt_BR/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,99 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# Bruno Barreto Freitas <brunobarretofreitas@outlook.com>, 2019.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-04-16 09:48-0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Bruno Barreto Freitas <brunobarretofreitas@outlook.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: registration/serializers.py:53
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr "\"View\" não está definida, passe-a como uma variável de contexto"
|
||||
|
||||
#: registration/serializers.py:58
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr "Defina \"adapter_class\" na view"
|
||||
|
||||
#: registration/serializers.py:77
|
||||
msgid "Define callback_url in view"
|
||||
msgstr "Defina \"callback_url\" na view"
|
||||
|
||||
#: registration/serializers.py:81
|
||||
msgid "Define client_class in view"
|
||||
msgstr "Defina \"client_class\" na view"
|
||||
|
||||
#: registration/serializers.py:102
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr "Entrada incorreta. \"access_token\" ou \"code\" são obrigatórios."
|
||||
|
||||
#: registration/serializers.py:111
|
||||
msgid "Incorrect value"
|
||||
msgstr "Valor incorreto"
|
||||
|
||||
#: registration/serializers.py:140
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr "Já existe um usuário cadastrado com este endereço de e-mail."
|
||||
|
||||
#: registration/serializers.py:148
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "Os dois campos de senha não correspondem."
|
||||
|
||||
#: registration/views.py:44
|
||||
msgid "Verification e-mail sent."
|
||||
msgstr "E-mail de verificação enviado."
|
||||
|
||||
#: registration/views.py:91
|
||||
msgid "ok"
|
||||
msgstr "Ok"
|
||||
|
||||
#: serializers.py:30
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr "Deve-se incluir \"email\" e \"password\"."
|
||||
|
||||
#: serializers.py:41
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "Deve-se incluir \"username\" e \"password\"."
|
||||
|
||||
#: serializers.py:54
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr "Deve-se incluir \"username\" ou \"email\" e \"password\"."
|
||||
|
||||
#: serializers.py:95
|
||||
msgid "User account is disabled."
|
||||
msgstr "Conta de usuário está desativada"
|
||||
|
||||
#: serializers.py:98
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "Não foi possível realizar o login com as credenciais fornecidas."
|
||||
|
||||
#: serializers.py:107
|
||||
msgid "E-mail is not verified."
|
||||
msgstr "E-mail não foi verificado."
|
||||
|
||||
#: views.py:126
|
||||
msgid "Successfully logged out."
|
||||
msgstr "Logout realizado com sucesso."
|
||||
|
||||
#: views.py:174
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr "E-mail de redefinição de senha foi enviado."
|
||||
|
||||
#: views.py:200
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr "Senha foi redefinida com a nova senha."
|
||||
|
||||
#: views.py:222
|
||||
msgid "New password has been saved."
|
||||
msgstr "Nova senha foi salva com sucesso."
|
95
dj_rest_auth/locale/tr/LC_MESSAGES/django.po
Normal file
95
dj_rest_auth/locale/tr/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,95 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-03-05 21:56-0800\n"
|
||||
"PO-Revision-Date: 2018-10-13 19:37+0300\n"
|
||||
"Language: tr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.2\n"
|
||||
|
||||
#: registration/serializers.py:67
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr "“View” tanımlanmadı, “context” değişkeni olarak tanımla"
|
||||
|
||||
#: registration/serializers.py:72
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr "“view” içerisinde “adapter_class” tanımla"
|
||||
|
||||
#: registration/serializers.py:91
|
||||
msgid "Define callback_url in view"
|
||||
msgstr "“view” içerisinde “callback_url” tanımla"
|
||||
|
||||
#: registration/serializers.py:95
|
||||
msgid "Define client_class in view"
|
||||
msgstr "“view” içerisinde “client_class” tanımla"
|
||||
|
||||
#: registration/serializers.py:116
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr "Geçersiz girdi. “access_token” veya “code” gerekli."
|
||||
|
||||
#: registration/serializers.py:125
|
||||
msgid "Incorrect value"
|
||||
msgstr "Geçersiz değer"
|
||||
|
||||
#: registration/serializers.py:185
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr "Bu e-posta adresi ile bir kullanıcı zaten kayıt olmuştu."
|
||||
|
||||
#: registration/serializers.py:193
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "İki şifre alanı eşleşmiyor."
|
||||
|
||||
#: registration/views.py:98
|
||||
msgid "ok"
|
||||
msgstr "tamam"
|
||||
|
||||
#: serializers.py:33
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr "\"email\" ve \"password\" içermelidir."
|
||||
|
||||
#: serializers.py:44
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "“username\" und \"password\" içermelidir."
|
||||
|
||||
#: serializers.py:57
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr "Ya ”username\" yada \"email\" ve \"password\" içermelidir."
|
||||
|
||||
#: serializers.py:98
|
||||
msgid "User account is disabled."
|
||||
msgstr "Kullanıcı hesap pasiftir."
|
||||
|
||||
#: serializers.py:101
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "Sağlanan kimlik bilgileri ile giriş yapılamıyor."
|
||||
|
||||
#: serializers.py:110
|
||||
msgid "E-mail is not verified."
|
||||
msgstr "E-posta adresi doğrulanmadı."
|
||||
|
||||
#: views.py:127
|
||||
msgid "Successfully logged out."
|
||||
msgstr "Başarılı bir şekilde çıkış yapıldı."
|
||||
|
||||
#: views.py:175
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr "Şifre sıfırlama e-postası gönderildi."
|
||||
|
||||
#: views.py:201
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr "Yeni şifre ile şifre sıfırlandı."
|
||||
|
||||
#: views.py:223
|
||||
msgid "New password has been saved."
|
||||
msgstr "Yeni şifre kaydedildi."
|
101
dj_rest_auth/locale/uk/django.po
Normal file
101
dj_rest_auth/locale/uk/django.po
Normal file
|
@ -0,0 +1,101 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-05-23 21:56-0800\n"
|
||||
"PO-Revision-Date: 2020-05-23 00:56+0300\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
|
||||
"%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
"X-Generator: Poedit 2.3.1\n"
|
||||
|
||||
#: registration/serializers.py:53
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr "View невідомий, передайте його як змінну контексту"
|
||||
|
||||
#: registration/serializers.py:58
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr "Встановіть adapter_class у view"
|
||||
|
||||
#: registration/serializers.py:77
|
||||
msgid "Define callback_url in view"
|
||||
msgstr "Встановіть callback_url у view"
|
||||
|
||||
#: registration/serializers.py:81
|
||||
msgid "Define client_class in view"
|
||||
msgstr "Встановіть client_class у view"
|
||||
|
||||
#: registration/serializers.py:102
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr "Некоректне введення. Необхідний access_token або code."
|
||||
|
||||
#: registration/serializers.py:111
|
||||
msgid "Incorrect value"
|
||||
msgstr "Некоректне значення"
|
||||
|
||||
#: registration/serializers.py:140
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr "Користувач з таким e-mail адресою вже зареєстрований."
|
||||
|
||||
#: registration/serializers.py:148
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "Паролі не збігаються."
|
||||
|
||||
#: registration/views.py:44
|
||||
msgid "Verification e-mail sent."
|
||||
msgstr "Лист з підтвердженням вислано на email."
|
||||
|
||||
#: registration/views.py:91
|
||||
msgid "ok"
|
||||
msgstr "добре"
|
||||
|
||||
#: serializers.py:30
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr "Має включати \"email\" и \"пароль\"."
|
||||
|
||||
#: serializers.py:41
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "Має включати \"юзернейм\" и \"пароль\"."
|
||||
|
||||
#: serializers.py:54
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr "Повинно включати або \"юзернейм\" либо \"email\" и \"пароль\"."
|
||||
|
||||
#: serializers.py:95
|
||||
msgid "User account is disabled."
|
||||
msgstr "Користувач вимкнений."
|
||||
|
||||
#: serializers.py:98
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "Неможливо увійти в систему з зазначеними обліковими даними."
|
||||
|
||||
#: serializers.py:107
|
||||
msgid "E-mail is not verified."
|
||||
msgstr "E-mail не підтверджено."
|
||||
|
||||
#: views.py:126
|
||||
msgid "Successfully logged out."
|
||||
msgstr "Успішно вийшли."
|
||||
|
||||
#: views.py:174
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr "Лист з інструкціями по відновленню пароля вислано."
|
||||
|
||||
#: views.py:200
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr "Пароль змінено на новий."
|
||||
|
||||
#: views.py:222
|
||||
msgid "New password has been saved."
|
||||
msgstr "Новий пароль збережений."
|
BIN
dj_rest_auth/locale/zh_Hans/LC_MESSAGES/django.mo
Normal file
BIN
dj_rest_auth/locale/zh_Hans/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
104
dj_rest_auth/locale/zh_Hans/LC_MESSAGES/django.po
Normal file
104
dj_rest_auth/locale/zh_Hans/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,104 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-28 11:41+0800\n"
|
||||
"PO-Revision-Date: 2018-10-28 11:45+0806\n"
|
||||
"Last-Translator: b' <admin@xx.com>'\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Translated-Using: django-rosetta 0.9.0\n"
|
||||
|
||||
#: registration/serializers.py:67
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr "View未定义,请通过context变量传入"
|
||||
|
||||
#: registration/serializers.py:72
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr "请在View中定义adapter_class"
|
||||
|
||||
#: registration/serializers.py:91
|
||||
msgid "Define callback_url in view"
|
||||
msgstr "请在view中定义callback_url"
|
||||
|
||||
#: registration/serializers.py:95
|
||||
msgid "Define client_class in view"
|
||||
msgstr "请在view中定义client_class"
|
||||
|
||||
#: registration/serializers.py:116
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr "输入错误。access_token或code是必填项。"
|
||||
|
||||
#: registration/serializers.py:125
|
||||
msgid "Incorrect value"
|
||||
msgstr "错误的值"
|
||||
|
||||
#: registration/serializers.py:139
|
||||
msgid "User is already registered with this e-mail address."
|
||||
msgstr "该邮箱地址已被注册。"
|
||||
|
||||
#: registration/serializers.py:185
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr "该邮箱地址已被注册。"
|
||||
|
||||
#: registration/serializers.py:193
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "两次输入的密码不相同"
|
||||
|
||||
#: registration/views.py:51
|
||||
msgid "Verification e-mail sent."
|
||||
msgstr "验证邮件已发送。"
|
||||
|
||||
#: registration/views.py:98
|
||||
msgid "ok"
|
||||
msgstr "好的"
|
||||
|
||||
#: serializers.py:33
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr "比如包含\"email\"和\"password\"。"
|
||||
|
||||
#: serializers.py:44
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "比如包含\"username\"和\"password\"。"
|
||||
|
||||
#: serializers.py:57
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr "比如包含\"username\",\"email\",\"password\"其中一个。"
|
||||
|
||||
#: serializers.py:98
|
||||
msgid "User account is disabled."
|
||||
msgstr "用户账号已被禁用。"
|
||||
|
||||
#: serializers.py:101
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "无法使用提供的信息登录。"
|
||||
|
||||
#: serializers.py:110
|
||||
msgid "E-mail is not verified."
|
||||
msgstr "邮箱未验证。"
|
||||
|
||||
#: views.py:127
|
||||
msgid "Successfully logged out."
|
||||
msgstr "已成功登出。"
|
||||
|
||||
#: views.py:175
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr "密码重置邮件已发送。"
|
||||
|
||||
#: views.py:201
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr "密码重置成功。"
|
||||
|
||||
#: views.py:223
|
||||
msgid "New password has been saved."
|
||||
msgstr "新密码已设置成功。"
|
BIN
dj_rest_auth/locale/zh_Hant/LC_MESSAGES/django.mo
Normal file
BIN
dj_rest_auth/locale/zh_Hant/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
103
dj_rest_auth/locale/zh_Hant/LC_MESSAGES/django.po
Normal file
103
dj_rest_auth/locale/zh_Hant/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,103 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-28 11:41+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: registration/serializers.py:67
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr ""
|
||||
|
||||
#: registration/serializers.py:72
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr ""
|
||||
|
||||
#: registration/serializers.py:91
|
||||
msgid "Define callback_url in view"
|
||||
msgstr ""
|
||||
|
||||
#: registration/serializers.py:95
|
||||
msgid "Define client_class in view"
|
||||
msgstr ""
|
||||
|
||||
#: registration/serializers.py:116
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr ""
|
||||
|
||||
#: registration/serializers.py:125
|
||||
msgid "Incorrect value"
|
||||
msgstr ""
|
||||
|
||||
#: registration/serializers.py:139
|
||||
msgid "User is already registered with this e-mail address."
|
||||
msgstr ""
|
||||
|
||||
#: registration/serializers.py:185
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr ""
|
||||
|
||||
#: registration/serializers.py:193
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr ""
|
||||
|
||||
#: registration/views.py:51
|
||||
msgid "Verification e-mail sent."
|
||||
msgstr ""
|
||||
|
||||
#: registration/views.py:98
|
||||
msgid "ok"
|
||||
msgstr ""
|
||||
|
||||
#: serializers.py:33
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr ""
|
||||
|
||||
#: serializers.py:44
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr ""
|
||||
|
||||
#: serializers.py:57
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr ""
|
||||
|
||||
#: serializers.py:98
|
||||
msgid "User account is disabled."
|
||||
msgstr ""
|
||||
|
||||
#: serializers.py:101
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr ""
|
||||
|
||||
#: serializers.py:110
|
||||
msgid "E-mail is not verified."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:127
|
||||
msgid "Successfully logged out."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:175
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:201
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:223
|
||||
msgid "New password has been saved."
|
||||
msgstr ""
|
4
dj_rest_auth/models.py
Normal file
4
dj_rest_auth/models.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from django.conf import settings
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
TokenModel = import_string(getattr(settings, 'REST_AUTH_TOKEN_MODEL', 'rest_framework.authtoken.models.Token'))
|
|
@ -1,15 +1,13 @@
|
|||
from dj_rest_auth.registration.serializers import \
|
||||
RegisterSerializer as DefaultRegisterSerializer
|
||||
from django.conf import settings
|
||||
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_auth.registration.serializers import (
|
||||
RegisterSerializer as DefaultRegisterSerializer)
|
||||
from ..utils import import_callable
|
||||
|
||||
from ..utils import import_callable
|
||||
|
||||
serializers = getattr(settings, 'REST_AUTH_REGISTER_SERIALIZERS', {})
|
||||
|
||||
RegisterSerializer = import_callable(
|
||||
serializers.get('REGISTER_SERIALIZER', DefaultRegisterSerializer))
|
||||
RegisterSerializer = import_callable(serializers.get('REGISTER_SERIALIZER', DefaultRegisterSerializer))
|
||||
|
||||
|
||||
def register_permission_classes():
|
|
@ -1,6 +1,8 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth import get_user_model
|
||||
from requests.exceptions import HTTPError
|
||||
from rest_framework import serializers
|
||||
|
||||
try:
|
||||
from allauth.account import app_settings as allauth_settings
|
||||
|
@ -14,9 +16,6 @@ try:
|
|||
except ImportError:
|
||||
raise ImportError("allauth needs to be added to INSTALLED_APPS.")
|
||||
|
||||
from rest_framework import serializers
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
|
||||
class SocialAccountSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
|
@ -80,6 +79,7 @@ class SocialLoginSerializer(serializers.Serializer):
|
|||
# Case 1: We received the access_token
|
||||
if attrs.get('access_token'):
|
||||
access_token = attrs.get('access_token')
|
||||
tokens_to_parse = {'access_token': access_token}
|
||||
|
||||
# Case 2: We received the authorization code
|
||||
elif attrs.get('code'):
|
||||
|
@ -113,12 +113,17 @@ class SocialLoginSerializer(serializers.Serializer):
|
|||
)
|
||||
token = client.get_access_token(code)
|
||||
access_token = token['access_token']
|
||||
tokens_to_parse = {'access_token': access_token}
|
||||
|
||||
# If available we add additional data to the dictionary
|
||||
for key in ["refresh_token", adapter.expires_in_key]:
|
||||
if key in token:
|
||||
tokens_to_parse[key] = token[key]
|
||||
else:
|
||||
raise serializers.ValidationError(
|
||||
_("Incorrect input. access_token or code is required."))
|
||||
|
||||
social_token = adapter.parse_token({'access_token': access_token})
|
||||
social_token = adapter.parse_token(tokens_to_parse)
|
||||
social_token.app = app
|
||||
|
||||
try:
|
|
@ -1,11 +1,11 @@
|
|||
from django.urls import path, re_path
|
||||
from django.views.generic import TemplateView
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import RegisterView, VerifyEmailView
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', RegisterView.as_view(), name='rest_register'),
|
||||
url(r'^verify-email/$', VerifyEmailView.as_view(), name='rest_verify_email'),
|
||||
path('', RegisterView.as_view(), name='rest_register'),
|
||||
path('verify-email/', VerifyEmailView.as_view(), name='rest_verify_email'),
|
||||
|
||||
# This url is used by django-allauth and empty TemplateView is
|
||||
# defined just to allow reverse() call inside app, for example when email
|
||||
|
@ -18,6 +18,6 @@ urlpatterns = [
|
|||
# If you don't want to use API on that step, then just use ConfirmEmailView
|
||||
# view from:
|
||||
# django-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py
|
||||
url(r'^account-confirm-email/(?P<key>[-:\w]+)/$', TemplateView.as_view(),
|
||||
re_path(r'^account-confirm-email/(?P<key>[-:\w]+)/$', TemplateView.as_view(),
|
||||
name='account_confirm_email'),
|
||||
]
|
|
@ -1,34 +1,30 @@
|
|||
from allauth.account import app_settings as allauth_settings
|
||||
from allauth.account.adapter import get_adapter
|
||||
from allauth.account.utils import complete_signup
|
||||
from allauth.account.views import ConfirmEmailView
|
||||
from allauth.socialaccount import signals
|
||||
from allauth.socialaccount.adapter import get_adapter as get_social_adapter
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from dj_rest_auth.app_settings import (JWTSerializer, TokenSerializer,
|
||||
create_token)
|
||||
from dj_rest_auth.models import TokenModel
|
||||
from dj_rest_auth.registration.serializers import (SocialAccountSerializer,
|
||||
SocialConnectSerializer,
|
||||
SocialLoginSerializer,
|
||||
VerifyEmailSerializer)
|
||||
from dj_rest_auth.utils import jwt_encode
|
||||
from dj_rest_auth.views import LoginView
|
||||
from django.conf import settings
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import (AllowAny,
|
||||
IsAuthenticated)
|
||||
from rest_framework.generics import CreateAPIView, ListAPIView, GenericAPIView
|
||||
from rest_framework.exceptions import NotFound
|
||||
from rest_framework import status
|
||||
from rest_framework.exceptions import NotFound, MethodNotAllowed
|
||||
from rest_framework.generics import CreateAPIView, GenericAPIView, ListAPIView
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from allauth.account.adapter import get_adapter
|
||||
from allauth.account.views import ConfirmEmailView
|
||||
from allauth.account.utils import complete_signup
|
||||
from allauth.account import app_settings as allauth_settings
|
||||
from allauth.socialaccount import signals
|
||||
from allauth.socialaccount.adapter import get_adapter as get_social_adapter
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
|
||||
from rest_auth.app_settings import (TokenSerializer,
|
||||
JWTSerializer,
|
||||
create_token)
|
||||
from rest_auth.models import TokenModel
|
||||
from rest_auth.registration.serializers import (VerifyEmailSerializer,
|
||||
SocialLoginSerializer,
|
||||
SocialAccountSerializer,
|
||||
SocialConnectSerializer)
|
||||
from rest_auth.utils import jwt_encode
|
||||
from rest_auth.views import LoginView
|
||||
from .app_settings import RegisterSerializer, register_permission_classes
|
||||
|
||||
sensitive_post_parameters_m = method_decorator(
|
||||
|
@ -40,6 +36,7 @@ class RegisterView(CreateAPIView):
|
|||
serializer_class = RegisterSerializer
|
||||
permission_classes = register_permission_classes()
|
||||
token_model = TokenModel
|
||||
throttle_scope = 'dj_rest_auth'
|
||||
|
||||
@sensitive_post_parameters_m
|
||||
def dispatch(self, *args, **kwargs):
|
||||
|
@ -53,11 +50,12 @@ class RegisterView(CreateAPIView):
|
|||
if getattr(settings, 'REST_USE_JWT', False):
|
||||
data = {
|
||||
'user': user,
|
||||
'token': self.token
|
||||
'access_token': self.access_token,
|
||||
'refresh_token': self.refresh_token
|
||||
}
|
||||
return JWTSerializer(data).data
|
||||
return JWTSerializer(data, context=self.get_serializer_context()).data
|
||||
else:
|
||||
return TokenSerializer(user.auth_token).data
|
||||
return TokenSerializer(user.auth_token, context=self.get_serializer_context()).data
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
|
@ -71,10 +69,12 @@ class RegisterView(CreateAPIView):
|
|||
|
||||
def perform_create(self, serializer):
|
||||
user = serializer.save(self.request)
|
||||
if getattr(settings, 'REST_USE_JWT', False):
|
||||
self.token = jwt_encode(user)
|
||||
else:
|
||||
create_token(self.token_model, user, serializer)
|
||||
if allauth_settings.EMAIL_VERIFICATION != \
|
||||
allauth_settings.EmailVerificationMethod.MANDATORY:
|
||||
if getattr(settings, 'REST_USE_JWT', False):
|
||||
self.access_token, self.refresh_token = jwt_encode(user)
|
||||
else:
|
||||
create_token(self.token_model, user, serializer)
|
||||
|
||||
complete_signup(self.request._request, user,
|
||||
allauth_settings.EMAIL_VERIFICATION,
|
||||
|
@ -89,6 +89,9 @@ class VerifyEmailView(APIView, ConfirmEmailView):
|
|||
def get_serializer(self, *args, **kwargs):
|
||||
return VerifyEmailSerializer(*args, **kwargs)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
raise MethodNotAllowed('GET')
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
|
@ -1,16 +1,18 @@
|
|||
from django.contrib.auth import get_user_model, authenticate
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate, get_user_model
|
||||
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.utils.http import urlsafe_base64_decode as uid_decoder
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from rest_framework import serializers, exceptions
|
||||
from django.utils.http import urlsafe_base64_decode as uid_decoder
|
||||
from django.utils.module_loading import import_string
|
||||
try:
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
except ImportError:
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import exceptions, serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from .models import TokenModel
|
||||
from .utils import import_callable
|
||||
|
||||
# Get the UserModel
|
||||
UserModel = get_user_model()
|
||||
|
@ -21,11 +23,14 @@ class LoginSerializer(serializers.Serializer):
|
|||
email = serializers.EmailField(required=False, allow_blank=True)
|
||||
password = serializers.CharField(style={'input_type': 'password'})
|
||||
|
||||
def authenticate(self, **kwargs):
|
||||
return authenticate(self.context['request'], **kwargs)
|
||||
|
||||
def _validate_email(self, email, password):
|
||||
user = None
|
||||
|
||||
if email and password:
|
||||
user = authenticate(email=email, password=password)
|
||||
user = self.authenticate(email=email, password=password)
|
||||
else:
|
||||
msg = _('Must include "email" and "password".')
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
@ -36,7 +41,7 @@ class LoginSerializer(serializers.Serializer):
|
|||
user = None
|
||||
|
||||
if username and password:
|
||||
user = authenticate(username=username, password=password)
|
||||
user = self.authenticate(username=username, password=password)
|
||||
else:
|
||||
msg = _('Must include "username" and "password".')
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
@ -47,9 +52,9 @@ class LoginSerializer(serializers.Serializer):
|
|||
user = None
|
||||
|
||||
if email and password:
|
||||
user = authenticate(email=email, password=password)
|
||||
user = self.authenticate(email=email, password=password)
|
||||
elif username and password:
|
||||
user = authenticate(username=username, password=password)
|
||||
user = self.authenticate(username=username, password=password)
|
||||
else:
|
||||
msg = _('Must include either "username" or "email" and "password".')
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
@ -99,7 +104,7 @@ class LoginSerializer(serializers.Serializer):
|
|||
raise exceptions.ValidationError(msg)
|
||||
|
||||
# If required, is the email verified?
|
||||
if 'rest_auth.registration' in settings.INSTALLED_APPS:
|
||||
if 'dj_rest_auth.registration' in settings.INSTALLED_APPS:
|
||||
from allauth.account import app_settings
|
||||
if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
|
||||
email_address = user.emailaddress_set.get(email=user.email)
|
||||
|
@ -134,7 +139,8 @@ class JWTSerializer(serializers.Serializer):
|
|||
"""
|
||||
Serializer for JWT authentication.
|
||||
"""
|
||||
token = serializers.CharField()
|
||||
access_token = serializers.CharField()
|
||||
refresh_token = serializers.CharField()
|
||||
user = serializers.SerializerMethodField()
|
||||
|
||||
def get_user(self, obj):
|
||||
|
@ -143,9 +149,14 @@ class JWTSerializer(serializers.Serializer):
|
|||
JWTSerializer. Defining it here to avoid circular imports
|
||||
"""
|
||||
rest_auth_serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {})
|
||||
JWTUserDetailsSerializer = import_callable(
|
||||
rest_auth_serializers.get('USER_DETAILS_SERIALIZER', UserDetailsSerializer)
|
||||
|
||||
JWTUserDetailsSerializer = import_string(
|
||||
rest_auth_serializers.get(
|
||||
'USER_DETAILS_SERIALIZER',
|
||||
'dj_rest_auth.serializers.UserDetailsSerializer'
|
||||
)
|
||||
)
|
||||
|
||||
user_data = JWTUserDetailsSerializer(obj['user'], context=self.context).data
|
||||
return user_data
|
||||
|
||||
|
@ -185,7 +196,7 @@ class PasswordResetSerializer(serializers.Serializer):
|
|||
|
||||
class PasswordResetConfirmSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for requesting a password reset e-mail.
|
||||
Serializer for confirming a password reset attempt.
|
||||
"""
|
||||
new_password1 = serializers.CharField(max_length=128)
|
||||
new_password2 = serializers.CharField(max_length=128)
|
||||
|
@ -207,6 +218,9 @@ class PasswordResetConfirmSerializer(serializers.Serializer):
|
|||
except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
|
||||
raise ValidationError({'uid': ['Invalid value']})
|
||||
|
||||
if not default_token_generator.check_token(self.user, attrs['token']):
|
||||
raise ValidationError({'token': ['Invalid value']})
|
||||
|
||||
self.custom_validation(attrs)
|
||||
# Construct SetPasswordForm instance
|
||||
self.set_password_form = self.set_password_form_class(
|
||||
|
@ -214,8 +228,6 @@ class PasswordResetConfirmSerializer(serializers.Serializer):
|
|||
)
|
||||
if not self.set_password_form.is_valid():
|
||||
raise serializers.ValidationError(self.set_password_form.errors)
|
||||
if not default_token_generator.check_token(self.user, attrs['token']):
|
||||
raise ValidationError({'token': ['Invalid value']})
|
||||
|
||||
return attrs
|
||||
|
||||
|
@ -253,7 +265,8 @@ class PasswordChangeSerializer(serializers.Serializer):
|
|||
)
|
||||
|
||||
if all(invalid_password_conditions):
|
||||
raise serializers.ValidationError('Invalid password')
|
||||
err_msg = _("Your old password was entered incorrectly. Please enter it again.")
|
||||
raise serializers.ValidationError(err_msg)
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
|
@ -1,6 +1,7 @@
|
|||
from django.conf import settings
|
||||
from django.http import HttpRequest
|
||||
from rest_framework import serializers
|
||||
|
||||
# Import is needed only if we are using social login, in which
|
||||
# case the allauth.socialaccount will be declared
|
||||
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
||||
|
@ -8,7 +9,7 @@ if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
|||
from allauth.socialaccount.models import SocialToken
|
||||
from allauth.socialaccount.providers.oauth.client import OAuthError
|
||||
|
||||
from rest_auth.registration.serializers import SocialConnectMixin
|
||||
from dj_rest_auth.registration.serializers import SocialConnectMixin
|
||||
|
||||
|
||||
class TwitterLoginSerializer(serializers.Serializer):
|
45
dj_rest_auth/tests/django_urls.py
Normal file
45
dj_rest_auth/tests/django_urls.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Moved in Django 1.8 from django to tests/auth_tests/urls.py
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.urls import urlpatterns
|
||||
|
||||
try:
|
||||
from django.contrib.auth.views import (
|
||||
logout, login, password_reset,
|
||||
password_change, password_reset_confirm
|
||||
)
|
||||
except ImportError:
|
||||
from django.contrib.auth.views import (
|
||||
LoginView, LogoutView, PasswordResetView,
|
||||
PasswordChangeView, PasswordResetConfirmView
|
||||
)
|
||||
logout = LogoutView.as_view()
|
||||
login = LoginView.as_view()
|
||||
password_reset = PasswordResetView.as_view()
|
||||
password_change = PasswordChangeView.as_view()
|
||||
password_reset_confirm = PasswordResetConfirmView.as_view()
|
||||
|
||||
|
||||
# special urls for auth test cases
|
||||
urlpatterns += [
|
||||
url(r'^logout/custom_query/$', logout, dict(redirect_field_name='follow')),
|
||||
url(r'^logout/next_page/$', logout, dict(next_page='/somewhere/')),
|
||||
url(r'^logout/next_page/named/$', logout, dict(next_page='password_reset')),
|
||||
url(r'^password_reset_from_email/$', password_reset, dict(from_email='staffmember@example.com')),
|
||||
url(r'^password_reset/custom_redirect/$', password_reset, dict(post_reset_redirect='/custom/')),
|
||||
url(r'^password_reset/custom_redirect/named/$', password_reset, dict(post_reset_redirect='password_reset')),
|
||||
url(r'^password_reset/html_email_template/$', password_reset,
|
||||
dict(html_email_template_name='registration/html_password_reset_email.html')),
|
||||
url(r'^reset/custom/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
|
||||
password_reset_confirm,
|
||||
dict(post_reset_redirect='/custom/')),
|
||||
url(r'^reset/custom/named/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
|
||||
password_reset_confirm,
|
||||
dict(post_reset_redirect='password_reset')),
|
||||
url(r'^password_change/custom/$', password_change, dict(post_change_redirect='/custom/')),
|
||||
url(r'^password_change/custom/named/$', password_change, dict(post_change_redirect='password_reset')),
|
||||
url(r'^admin_password_reset/$', password_reset, dict(is_admin_site=True)),
|
||||
url(r'^login_required/$', login_required(password_reset)),
|
||||
url(r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),
|
||||
]
|
|
@ -1,11 +1,9 @@
|
|||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.test.client import Client, MULTIPART_CONTENT
|
||||
from django.test.client import MULTIPART_CONTENT, Client
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework import permissions
|
||||
from rest_framework import permissions, status
|
||||
|
||||
try:
|
||||
from django.urls import reverse
|
6
dj_rest_auth/tests/requirements.pip
Normal file
6
dj_rest_auth/tests/requirements.pip
Normal file
|
@ -0,0 +1,6 @@
|
|||
django-allauth>=0.25.0
|
||||
responses>=0.5.0
|
||||
flake8==2.4.0
|
||||
djangorestframework-simplejwt==4.4.0
|
||||
unittest-xml-reporting>=3.0.2
|
||||
coveralls>=1.11.1
|
|
@ -1,8 +1,11 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
PROJECT_ROOT = os.path.abspath(os.path.split(os.path.split(__file__)[0])[0])
|
||||
|
||||
|
||||
logging.disable(logging.CRITICAL)
|
||||
ROOT_URLCONF = 'urls'
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = '%s/staticserve' % PROJECT_ROOT
|
||||
|
@ -65,11 +68,15 @@ TEMPLATES = [
|
|||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
|
||||
)
|
||||
}
|
||||
|
||||
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
|
||||
TEST_OUTPUT_DIR = 'test-results'
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.messages',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.humanize',
|
||||
|
@ -88,10 +95,10 @@ INSTALLED_APPS = [
|
|||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
|
||||
'rest_auth',
|
||||
'rest_auth.registration',
|
||||
'dj_rest_auth',
|
||||
'dj_rest_auth.registration',
|
||||
|
||||
'rest_framework_jwt'
|
||||
'rest_framework_simplejwt.token_blacklist'
|
||||
]
|
||||
|
||||
SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd"
|
|
@ -1,23 +1,36 @@
|
|||
from django.test import TestCase, override_settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core import mail
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import force_text
|
||||
import json
|
||||
|
||||
from allauth.account import app_settings as account_app_settings
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core import mail
|
||||
from django.test import TestCase, override_settings
|
||||
from django.utils.encoding import force_text
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIRequestFactory
|
||||
|
||||
from rest_auth.registration.views import RegisterView
|
||||
from rest_auth.registration.app_settings import register_permission_classes
|
||||
|
||||
from .mixins import TestsMixin, CustomPermissionClass
|
||||
from dj_rest_auth.registration.app_settings import register_permission_classes
|
||||
from dj_rest_auth.registration.views import RegisterView
|
||||
from .mixins import CustomPermissionClass, TestsMixin
|
||||
|
||||
try:
|
||||
from django.urls import reverse
|
||||
except ImportError:
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
||||
from jwt import decode as decode_jwt
|
||||
|
||||
class TESTTokenObtainPairSerializer(TokenObtainPairSerializer):
|
||||
@classmethod
|
||||
def get_token(cls, user):
|
||||
token = super().get_token(user)
|
||||
# Add custom claims
|
||||
token['name'] = user.username
|
||||
token['email'] = user.email
|
||||
|
||||
return token
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF="tests.urls")
|
||||
class APIBasicTests(TestsMixin, TestCase):
|
||||
|
@ -154,8 +167,8 @@ class APIBasicTests(TestsMixin, TestCase):
|
|||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
|
||||
self.post(self.login_url, data=payload, status_code=200)
|
||||
self.assertEqual('token' in self.response.json.keys(), True)
|
||||
self.token = self.response.json['token']
|
||||
self.assertEqual('access_token' in self.response.json.keys(), True)
|
||||
self.token = self.response.json['access_token']
|
||||
|
||||
def test_login_by_email(self):
|
||||
# starting test without allauth app
|
||||
|
@ -384,7 +397,7 @@ class APIBasicTests(TestsMixin, TestCase):
|
|||
"password": self.PASS
|
||||
}
|
||||
self.post(self.login_url, data=payload, status_code=200)
|
||||
self.token = self.response.json['token']
|
||||
self.token = self.response.json['access_token']
|
||||
self.get(self.user_url, status_code=200)
|
||||
|
||||
self.patch(self.user_url, data=self.BASIC_USER_DATA, status_code=200)
|
||||
|
@ -409,7 +422,6 @@ class APIBasicTests(TestsMixin, TestCase):
|
|||
|
||||
@override_settings(REST_AUTH_REGISTER_PERMISSION_CLASSES=(CustomPermissionClass,))
|
||||
def test_registration_with_custom_permission_class(self):
|
||||
|
||||
class CustomRegisterView(RegisterView):
|
||||
permission_classes = register_permission_classes()
|
||||
authentication_classes = ()
|
||||
|
@ -428,7 +440,7 @@ class APIBasicTests(TestsMixin, TestCase):
|
|||
self.post(self.register_url, data={}, status_code=400)
|
||||
|
||||
result = self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201)
|
||||
self.assertIn('token', result.data)
|
||||
self.assertIn('access_token', result.data)
|
||||
self.assertEqual(get_user_model().objects.all().count(), user_count + 1)
|
||||
|
||||
self._login()
|
||||
|
@ -467,6 +479,13 @@ class APIBasicTests(TestsMixin, TestCase):
|
|||
new_user = get_user_model().objects.latest('id')
|
||||
self.assertEqual(new_user.username, self.REGISTRATION_DATA['username'])
|
||||
|
||||
# test browsable endpoint
|
||||
result = self.get(
|
||||
self.verify_email_url
|
||||
)
|
||||
self.assertEqual(result.status_code, 405)
|
||||
self.assertEqual(result.json['detail'], 'Method "GET" not allowed.')
|
||||
|
||||
# email is not verified yet
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
|
@ -479,7 +498,7 @@ class APIBasicTests(TestsMixin, TestCase):
|
|||
)
|
||||
|
||||
# verify email
|
||||
email_confirmation = new_user.emailaddress_set.get(email=self.EMAIL)\
|
||||
email_confirmation = new_user.emailaddress_set.get(email=self.EMAIL) \
|
||||
.emailconfirmation_set.order_by('-created')[0]
|
||||
self.post(
|
||||
self.verify_email_url,
|
||||
|
@ -516,3 +535,336 @@ class APIBasicTests(TestsMixin, TestCase):
|
|||
|
||||
self.post(self.login_url, data=payload, status_code=status.HTTP_200_OK)
|
||||
self.get(self.logout_url, status_code=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
@override_settings(JWT_AUTH_COOKIE='jwt-auth')
|
||||
def test_login_jwt_sets_cookie(self):
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
resp = self.post(self.login_url, data=payload, status_code=200)
|
||||
self.assertTrue('jwt-auth' in resp.cookies.keys())
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
@override_settings(JWT_AUTH_COOKIE='jwt-auth')
|
||||
def test_logout_jwt_deletes_cookie(self):
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
self.post(self.login_url, data=payload, status_code=200)
|
||||
resp = self.post(self.logout_url, status=200)
|
||||
self.assertEqual('', resp.cookies.get('jwt-auth').value)
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
@override_settings(JWT_AUTH_COOKIE='jwt-auth')
|
||||
@override_settings(REST_FRAMEWORK=dict(
|
||||
DEFAULT_AUTHENTICATION_CLASSES=[
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||
]
|
||||
))
|
||||
@override_settings(REST_SESSION_LOGIN=False)
|
||||
def test_cookie_authentication(self):
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
resp = self.post(self.login_url, data=payload, status_code=200)
|
||||
self.assertEqual(['jwt-auth'], list(resp.cookies.keys()))
|
||||
resp = self.get('/protected-view/')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
def test_blacklisting_not_installed(self):
|
||||
settings.INSTALLED_APPS.remove('rest_framework_simplejwt.token_blacklist')
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
resp = self.post(self.login_url, data=payload, status_code=200)
|
||||
token = resp.data['refresh_token']
|
||||
resp = self.post(self.logout_url, status=200, data={'refresh': token})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(resp.data["detail"],
|
||||
"Neither cookies or blacklist are enabled, so the token has not been deleted server side. "
|
||||
"Please make sure the token is deleted client side.")
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
def test_blacklisting(self):
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
resp = self.post(self.login_url, data=payload, status_code=200)
|
||||
token = resp.data['refresh_token']
|
||||
# test refresh token not included in request data
|
||||
resp = self.post(self.logout_url, status=200)
|
||||
self.assertEqual(resp.status_code, 401)
|
||||
# test token is invalid or expired
|
||||
resp = self.post(self.logout_url, status=200, data={'refresh': '1'})
|
||||
self.assertEqual(resp.status_code, 401)
|
||||
# test successful logout
|
||||
resp = self.post(self.logout_url, status=200, data={'refresh': token})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
# test token is blacklisted
|
||||
resp = self.post(self.logout_url, status=200, data={'refresh': token})
|
||||
self.assertEqual(resp.status_code, 401)
|
||||
# test other TokenError, AttributeError, TypeError (invalid format)
|
||||
resp = self.post(self.logout_url, status=200, data=json.dumps({'refresh': token}))
|
||||
self.assertEqual(resp.status_code, 500)
|
||||
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
@override_settings(JWT_AUTH_COOKIE=None)
|
||||
@override_settings(REST_FRAMEWORK=dict(
|
||||
DEFAULT_AUTHENTICATION_CLASSES=[
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||
]
|
||||
))
|
||||
@override_settings(REST_SESSION_LOGIN=False)
|
||||
@override_settings(
|
||||
REST_AUTH_SERIALIZERS={
|
||||
"JWT_TOKEN_CLAIMS_SERIALIZER": 'tests.test_api.TESTTokenObtainPairSerializer'
|
||||
}
|
||||
)
|
||||
def test_custom_jwt_claims(self):
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS)
|
||||
|
||||
self.post(self.login_url, data=payload, status_code=200)
|
||||
self.assertEqual('access_token' in self.response.json.keys(), True)
|
||||
self.token = self.response.json['access_token']
|
||||
claims = decode_jwt(self.token, settings.SECRET_KEY, algorithms='HS256')
|
||||
self.assertEquals(claims['user_id'], 1)
|
||||
self.assertEquals(claims['name'], 'person')
|
||||
self.assertEquals(claims['email'], 'person1@world.com')
|
||||
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
@override_settings(JWT_AUTH_COOKIE='jwt-auth')
|
||||
@override_settings(REST_FRAMEWORK=dict(
|
||||
DEFAULT_AUTHENTICATION_CLASSES=[
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||
]
|
||||
))
|
||||
@override_settings(REST_SESSION_LOGIN=False)
|
||||
@override_settings(
|
||||
REST_AUTH_SERIALIZERS={
|
||||
"JWT_TOKEN_CLAIMS_SERIALIZER": 'tests.test_api.TESTTokenObtainPairSerializer'
|
||||
}
|
||||
)
|
||||
def test_custom_jwt_claims_cookie_w_authentication(self):
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS)
|
||||
resp = self.post(self.login_url, data=payload, status_code=200)
|
||||
self.assertEqual(['jwt-auth'], list(resp.cookies.keys()))
|
||||
token = resp.cookies.get('jwt-auth').value
|
||||
claims = decode_jwt(token, settings.SECRET_KEY, algorithms='HS256')
|
||||
self.assertEquals(claims['user_id'], 1)
|
||||
self.assertEquals(claims['name'], 'person')
|
||||
self.assertEquals(claims['email'], 'person1@world.com')
|
||||
resp = self.get('/protected-view/')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
@override_settings(JWT_AUTH_COOKIE='jwt-auth')
|
||||
@override_settings(JWT_AUTH_COOKIE_USE_CSRF=False)
|
||||
@override_settings(JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED=False)
|
||||
@override_settings(REST_FRAMEWORK=dict(
|
||||
DEFAULT_AUTHENTICATION_CLASSES=[
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||
]
|
||||
))
|
||||
@override_settings(REST_SESSION_LOGIN=False)
|
||||
@override_settings(CSRF_COOKIE_SECURE =True)
|
||||
@override_settings(CSRF_COOKIE_HTTPONLY =True)
|
||||
def test_wo_csrf_enforcement(self):
|
||||
from .mixins import APIClient
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
client = APIClient(enforce_csrf_checks=True)
|
||||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
|
||||
resp = client.post(self.login_url, payload)
|
||||
self.assertTrue('jwt-auth' in list(client.cookies.keys()))
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
## TEST WITH JWT AUTH HEADER
|
||||
jwtclient = APIClient(enforce_csrf_checks=True)
|
||||
token = resp.data['access_token']
|
||||
resp = jwtclient.get('/protected-view/', HTTP_AUTHORIZATION='Bearer '+token)
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
resp = jwtclient.post('/protected-view/', {}, HTTP_AUTHORIZATION='Bearer '+token)
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
## TEST WITH COOKIES
|
||||
resp = client.get('/protected-view/')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
resp = client.post('/protected-view/', {})
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
@override_settings(JWT_AUTH_COOKIE='jwt-auth')
|
||||
@override_settings(JWT_AUTH_COOKIE_USE_CSRF=True)
|
||||
@override_settings(JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED=False)
|
||||
@override_settings(REST_FRAMEWORK=dict(
|
||||
DEFAULT_AUTHENTICATION_CLASSES=[
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||
]
|
||||
))
|
||||
@override_settings(REST_SESSION_LOGIN=False)
|
||||
@override_settings(CSRF_COOKIE_SECURE =True)
|
||||
@override_settings(CSRF_COOKIE_HTTPONLY =True)
|
||||
def test_csrf_wo_login_csrf_enforcement(self):
|
||||
from .mixins import APIClient
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
client = APIClient(enforce_csrf_checks=True)
|
||||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
|
||||
response = client.get(reverse("getcsrf"))
|
||||
csrftoken = client.cookies['csrftoken'].value
|
||||
|
||||
resp = client.post(self.login_url, payload)
|
||||
self.assertTrue('jwt-auth' in list(client.cookies.keys()))
|
||||
self.assertTrue('csrftoken' in list(client.cookies.keys()))
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
## TEST WITH JWT AUTH HEADER
|
||||
jwtclient = APIClient(enforce_csrf_checks=True)
|
||||
token = resp.data['access_token']
|
||||
resp = jwtclient.get('/protected-view/')
|
||||
self.assertEquals(resp.status_code, 403)
|
||||
resp = jwtclient.get('/protected-view/', HTTP_AUTHORIZATION='Bearer '+token)
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
resp = jwtclient.post('/protected-view/', {})
|
||||
self.assertEquals(resp.status_code, 403)
|
||||
resp = jwtclient.post('/protected-view/', {}, HTTP_AUTHORIZATION='Bearer '+token)
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
## TEST WITH COOKIES
|
||||
resp = client.get('/protected-view/')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
#fail w/o csrftoken in payload
|
||||
resp = client.post('/protected-view/', {})
|
||||
self.assertEquals(resp.status_code, 403)
|
||||
|
||||
csrfparam = {"csrfmiddlewaretoken": csrftoken}
|
||||
resp = client.post('/protected-view/', csrfparam)
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
@override_settings(JWT_AUTH_COOKIE='jwt-auth')
|
||||
@override_settings(JWT_AUTH_COOKIE_USE_CSRF=True)
|
||||
@override_settings(JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED=True) #True at your own risk
|
||||
@override_settings(REST_FRAMEWORK=dict(
|
||||
DEFAULT_AUTHENTICATION_CLASSES=[
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||
]
|
||||
))
|
||||
@override_settings(REST_SESSION_LOGIN=False)
|
||||
@override_settings(CSRF_COOKIE_SECURE =True)
|
||||
@override_settings(CSRF_COOKIE_HTTPONLY =True)
|
||||
def test_csrf_w_login_csrf_enforcement(self):
|
||||
from .mixins import APIClient
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
client = APIClient(enforce_csrf_checks=True)
|
||||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
|
||||
response = client.get(reverse("getcsrf"))
|
||||
csrftoken = client.cookies['csrftoken'].value
|
||||
|
||||
#fail w/o csrftoken in payload
|
||||
resp = client.post(self.login_url, payload)
|
||||
self.assertEquals(resp.status_code, 403)
|
||||
|
||||
payload['csrfmiddlewaretoken'] = csrftoken
|
||||
resp = client.post(self.login_url, payload)
|
||||
self.assertTrue('jwt-auth' in list(client.cookies.keys()))
|
||||
self.assertTrue('csrftoken' in list(client.cookies.keys()))
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
## TEST WITH JWT AUTH HEADER does not make sense
|
||||
|
||||
## TEST WITH COOKIES
|
||||
resp = client.get('/protected-view/')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
#fail w/o csrftoken in payload
|
||||
resp = client.post('/protected-view/', {})
|
||||
self.assertEquals(resp.status_code, 403)
|
||||
|
||||
csrfparam = {"csrfmiddlewaretoken": csrftoken}
|
||||
resp = client.post('/protected-view/', csrfparam)
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
|
||||
@override_settings(REST_USE_JWT=True)
|
||||
@override_settings(JWT_AUTH_COOKIE='jwt-auth')
|
||||
@override_settings(JWT_AUTH_COOKIE_USE_CSRF=False)
|
||||
@override_settings(JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED=True) #True at your own risk
|
||||
@override_settings(REST_FRAMEWORK=dict(
|
||||
DEFAULT_AUTHENTICATION_CLASSES=[
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||
]
|
||||
))
|
||||
@override_settings(REST_SESSION_LOGIN=False)
|
||||
@override_settings(CSRF_COOKIE_SECURE =True)
|
||||
@override_settings(CSRF_COOKIE_HTTPONLY =True)
|
||||
def test_csrf_w_login_csrf_enforcement_2(self):
|
||||
from .mixins import APIClient
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
client = APIClient(enforce_csrf_checks=True)
|
||||
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||
|
||||
response = client.get(reverse("getcsrf"))
|
||||
csrftoken = client.cookies['csrftoken'].value
|
||||
|
||||
#fail w/o csrftoken in payload
|
||||
resp = client.post(self.login_url, payload)
|
||||
self.assertEquals(resp.status_code, 403)
|
||||
|
||||
payload['csrfmiddlewaretoken'] = csrftoken
|
||||
resp = client.post(self.login_url, payload)
|
||||
self.assertTrue('jwt-auth' in list(client.cookies.keys()))
|
||||
self.assertTrue('csrftoken' in list(client.cookies.keys()))
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
## TEST WITH JWT AUTH HEADER does not make sense
|
||||
|
||||
## TEST WITH COOKIES
|
||||
resp = client.get('/protected-view/')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
#fail w/o csrftoken in payload
|
||||
resp = client.post('/protected-view/', {})
|
||||
self.assertEquals(resp.status_code, 403)
|
||||
|
||||
csrfparam = {"csrfmiddlewaretoken": csrftoken}
|
||||
resp = client.post('/protected-view/', csrfparam)
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
|
@ -1,23 +1,21 @@
|
|||
import json
|
||||
|
||||
from django.test import TestCase
|
||||
import responses
|
||||
from allauth.socialaccount.models import SocialApp
|
||||
from allauth.socialaccount.providers.facebook.provider import GRAPH_API_URL
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test.utils import override_settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from rest_framework import status
|
||||
|
||||
from .mixins import TestsMixin
|
||||
|
||||
try:
|
||||
from django.urls import reverse
|
||||
except ImportError:
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from allauth.socialaccount.models import SocialApp
|
||||
from allauth.socialaccount.providers.facebook.provider import GRAPH_API_URL
|
||||
import responses
|
||||
|
||||
from rest_framework import status
|
||||
|
||||
from .mixins import TestsMixin
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF="tests.urls")
|
||||
class TestSocialAuth(TestsMixin, TestCase):
|
||||
|
@ -304,7 +302,7 @@ class TestSocialAuth(TestsMixin, TestCase):
|
|||
}
|
||||
|
||||
self.post(self.fb_login_url, data=payload, status_code=200)
|
||||
self.assertIn('token', self.response.json.keys())
|
||||
self.assertIn('access_token', self.response.json.keys())
|
||||
self.assertIn('user', self.response.json.keys())
|
||||
|
||||
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
|
|
@ -1,20 +1,33 @@
|
|||
from django.conf.urls import url, include
|
||||
from allauth.socialaccount.providers.facebook.views import \
|
||||
FacebookOAuth2Adapter
|
||||
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
|
||||
from dj_rest_auth.registration.views import (SocialAccountDisconnectView,
|
||||
SocialAccountListView,
|
||||
SocialConnectView,
|
||||
SocialLoginView)
|
||||
from dj_rest_auth.social_serializers import (TwitterConnectSerializer,
|
||||
TwitterLoginSerializer)
|
||||
from dj_rest_auth.urls import urlpatterns
|
||||
from django.conf.urls import include, url
|
||||
from django.views.generic import TemplateView
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from rest_framework import permissions
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from . import django_urls
|
||||
|
||||
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
||||
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
|
||||
|
||||
from rest_framework.decorators import api_view
|
||||
class ExampleProtectedView(APIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
from rest_auth.urls import urlpatterns
|
||||
from rest_auth.registration.views import (
|
||||
SocialLoginView, SocialConnectView, SocialAccountListView,
|
||||
SocialAccountDisconnectView
|
||||
)
|
||||
from rest_auth.social_serializers import (
|
||||
TwitterLoginSerializer, TwitterConnectSerializer
|
||||
)
|
||||
def get(self, *args, **kwargs):
|
||||
return Response(dict(success=True))
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return Response(dict(success=True))
|
||||
|
||||
|
||||
class FacebookLogin(SocialLoginView):
|
||||
|
@ -51,9 +64,14 @@ def twitter_login_view(request):
|
|||
class TwitterLoginNoAdapter(SocialLoginView):
|
||||
serializer_class = TwitterLoginSerializer
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@api_view(['GET'])
|
||||
def get_csrf_cookie(request):
|
||||
return Response()
|
||||
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^rest-registration/', include('rest_auth.registration.urls')),
|
||||
url(r'^rest-registration/', include('dj_rest_auth.registration.urls')),
|
||||
url(r'^test-admin/', include(django_urls)),
|
||||
url(r'^account-email-verification-sent/$', TemplateView.as_view(),
|
||||
name='account_email_verification_sent'),
|
||||
|
@ -66,7 +84,9 @@ urlpatterns += [
|
|||
url(r'^social-login/facebook/connect/$', FacebookConnect.as_view(), name='fb_connect'),
|
||||
url(r'^social-login/twitter/connect/$', TwitterConnect.as_view(), name='tw_connect'),
|
||||
url(r'^socialaccounts/$', SocialAccountListView.as_view(), name='social_account_list'),
|
||||
url(r'^protected-view/$', ExampleProtectedView.as_view()),
|
||||
url(r'^socialaccounts/(?P<pk>\d+)/disconnect/$', SocialAccountDisconnectView.as_view(),
|
||||
name='social_account_disconnect'),
|
||||
url(r'^accounts/', include('allauth.socialaccount.urls'))
|
||||
url(r'^accounts/', include('allauth.socialaccount.urls')),
|
||||
url(r'^getcsrf/', get_csrf_cookie, name='getcsrf'),
|
||||
]
|
26
dj_rest_auth/urls.py
Normal file
26
dj_rest_auth/urls.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from dj_rest_auth.views import (LoginView, LogoutView, PasswordChangeView,
|
||||
PasswordResetConfirmView, PasswordResetView,
|
||||
UserDetailsView)
|
||||
from django.urls import path
|
||||
from django.conf import settings
|
||||
|
||||
urlpatterns = [
|
||||
# URLs that do not require a session or valid token
|
||||
path('password/reset/', PasswordResetView.as_view(), name='rest_password_reset'),
|
||||
path('password/reset/confirm/', PasswordResetConfirmView.as_view(), name='rest_password_reset_confirm'),
|
||||
path('login/', LoginView.as_view(), name='rest_login'),
|
||||
# URLs that require a user to be logged in with a valid session / token.
|
||||
path('logout/', LogoutView.as_view(), name='rest_logout'),
|
||||
path('user/', UserDetailsView.as_view(), name='rest_user_details'),
|
||||
path('password/change/', PasswordChangeView.as_view(), name='rest_password_change'),
|
||||
]
|
||||
|
||||
if getattr(settings, 'REST_USE_JWT', False):
|
||||
from rest_framework_simplejwt.views import (
|
||||
TokenRefreshView, TokenVerifyView,
|
||||
)
|
||||
|
||||
urlpatterns += [
|
||||
path('token/verify/', TokenVerifyView.as_view(), name='token_verify'),
|
||||
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
||||
]
|
37
dj_rest_auth/utils.py
Normal file
37
dj_rest_auth/utils.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def import_callable(path_or_callable):
|
||||
if hasattr(path_or_callable, '__call__'):
|
||||
return path_or_callable
|
||||
else:
|
||||
assert isinstance(path_or_callable, str)
|
||||
package, attr = path_or_callable.rsplit('.', 1)
|
||||
return getattr(import_module(package), attr)
|
||||
|
||||
|
||||
def default_create_token(token_model, user, serializer):
|
||||
token, _ = token_model.objects.get_or_create(user=user)
|
||||
return token
|
||||
|
||||
|
||||
def jwt_encode(user):
|
||||
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
||||
rest_auth_serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {})
|
||||
|
||||
JWTTokenClaimsSerializer = rest_auth_serializers.get(
|
||||
'JWT_TOKEN_CLAIMS_SERIALIZER',
|
||||
TokenObtainPairSerializer
|
||||
)
|
||||
|
||||
TOPS = import_callable(JWTTokenClaimsSerializer)
|
||||
refresh = TOPS.get_token(user)
|
||||
return refresh.access_token, refresh
|
||||
|
||||
|
||||
try:
|
||||
from .jwt_auth import JWTCookieAuthentication
|
||||
except ImportError:
|
||||
pass
|
|
@ -1,25 +1,22 @@
|
|||
from django.contrib.auth import (
|
||||
login as django_login,
|
||||
logout as django_logout
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth import login as django_login
|
||||
from django.contrib.auth import logout as django_logout
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .app_settings import (
|
||||
TokenSerializer, UserDetailsSerializer, LoginSerializer,
|
||||
PasswordResetSerializer, PasswordResetConfirmSerializer,
|
||||
PasswordChangeSerializer, JWTSerializer, create_token
|
||||
)
|
||||
from .app_settings import (JWTSerializer, LoginSerializer,
|
||||
PasswordChangeSerializer,
|
||||
PasswordResetConfirmSerializer,
|
||||
PasswordResetSerializer, TokenSerializer,
|
||||
UserDetailsSerializer, create_token)
|
||||
from .models import TokenModel
|
||||
from .utils import jwt_encode
|
||||
|
||||
|
@ -43,6 +40,7 @@ class LoginView(GenericAPIView):
|
|||
permission_classes = (AllowAny,)
|
||||
serializer_class = LoginSerializer
|
||||
token_model = TokenModel
|
||||
throttle_scope = 'dj_rest_auth'
|
||||
|
||||
@sensitive_post_parameters_m
|
||||
def dispatch(self, *args, **kwargs):
|
||||
|
@ -62,7 +60,7 @@ class LoginView(GenericAPIView):
|
|||
self.user = self.serializer.validated_data['user']
|
||||
|
||||
if getattr(settings, 'REST_USE_JWT', False):
|
||||
self.token = jwt_encode(self.user)
|
||||
self.access_token, self.refresh_token = jwt_encode(self.user)
|
||||
else:
|
||||
self.token = create_token(self.token_model, self.user,
|
||||
self.serializer)
|
||||
|
@ -76,20 +74,38 @@ class LoginView(GenericAPIView):
|
|||
if getattr(settings, 'REST_USE_JWT', False):
|
||||
data = {
|
||||
'user': self.user,
|
||||
'token': self.token
|
||||
'access_token': self.access_token,
|
||||
'refresh_token': self.refresh_token
|
||||
}
|
||||
serializer = serializer_class(instance=data,
|
||||
context={'request': self.request})
|
||||
context=self.get_serializer_context())
|
||||
else:
|
||||
serializer = serializer_class(instance=self.token,
|
||||
context={'request': self.request})
|
||||
context=self.get_serializer_context())
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
response = Response(serializer.data, status=status.HTTP_200_OK)
|
||||
if getattr(settings, 'REST_USE_JWT', False):
|
||||
cookie_name = getattr(settings, 'JWT_AUTH_COOKIE', None)
|
||||
cookie_secure = getattr(settings, 'JWT_AUTH_SECURE', False)
|
||||
cookie_httponly = getattr(settings, 'JWT_AUTH_HTTPONLY', True)
|
||||
cookie_samesite = getattr(settings, 'JWT_AUTH_SAMESITE', 'Lax')
|
||||
from rest_framework_simplejwt.settings import api_settings as jwt_settings
|
||||
if cookie_name:
|
||||
from datetime import datetime
|
||||
expiration = (datetime.utcnow() + jwt_settings.ACCESS_TOKEN_LIFETIME)
|
||||
response.set_cookie(
|
||||
cookie_name,
|
||||
self.access_token,
|
||||
expires=expiration,
|
||||
secure=cookie_secure,
|
||||
httponly=cookie_httponly,
|
||||
samesite=cookie_samesite
|
||||
)
|
||||
return response
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.request = request
|
||||
self.serializer = self.get_serializer(data=self.request.data,
|
||||
context={'request': request})
|
||||
self.serializer = self.get_serializer(data=self.request.data)
|
||||
self.serializer.is_valid(raise_exception=True)
|
||||
|
||||
self.login()
|
||||
|
@ -104,6 +120,7 @@ class LogoutView(APIView):
|
|||
Accepts/Returns nothing.
|
||||
"""
|
||||
permission_classes = (AllowAny,)
|
||||
throttle_scope = 'dj_rest_auth'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if getattr(settings, 'ACCOUNT_LOGOUT_ON_GET', False):
|
||||
|
@ -122,10 +139,54 @@ class LogoutView(APIView):
|
|||
except (AttributeError, ObjectDoesNotExist):
|
||||
pass
|
||||
|
||||
django_logout(request)
|
||||
if getattr(settings, 'REST_SESSION_LOGIN', True):
|
||||
django_logout(request)
|
||||
|
||||
return Response({"detail": _("Successfully logged out.")},
|
||||
status=status.HTTP_200_OK)
|
||||
response = Response({"detail": _("Successfully logged out.")},
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
if getattr(settings, 'REST_USE_JWT', False):
|
||||
# NOTE: this import occurs here rather than at the top level
|
||||
# because JWT support is optional, and if `REST_USE_JWT` isn't
|
||||
# True we shouldn't need the dependency
|
||||
from rest_framework_simplejwt.exceptions import TokenError
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
|
||||
cookie_name = getattr(settings, 'JWT_AUTH_COOKIE', None)
|
||||
if cookie_name:
|
||||
response.delete_cookie(cookie_name)
|
||||
|
||||
elif 'rest_framework_simplejwt.token_blacklist' in settings.INSTALLED_APPS:
|
||||
# add refresh token to blacklist
|
||||
try:
|
||||
token = RefreshToken(request.data['refresh'])
|
||||
token.blacklist()
|
||||
|
||||
except KeyError:
|
||||
response = Response({"detail": _("Refresh token was not included in request data.")},
|
||||
status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
except (TokenError, AttributeError, TypeError) as error:
|
||||
if hasattr(error, 'args'):
|
||||
if 'Token is blacklisted' in error.args or 'Token is invalid or expired' in error.args:
|
||||
response = Response({"detail": _(error.args[0])},
|
||||
status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
else:
|
||||
response = Response({"detail": _("An error has occurred.")},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
else:
|
||||
response = Response({"detail": _("An error has occurred.")},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
else:
|
||||
response = Response({
|
||||
"detail": _("Neither cookies or blacklist are enabled, so the token has not been deleted server "
|
||||
"side. Please make sure the token is deleted client side."
|
||||
)}, status=status.HTTP_200_OK)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class UserDetailsView(RetrieveUpdateAPIView):
|
||||
|
@ -149,7 +210,6 @@ class UserDetailsView(RetrieveUpdateAPIView):
|
|||
"""
|
||||
Adding this method since it is sometimes called when using
|
||||
django-rest-swagger
|
||||
https://github.com/Tivix/django-rest-auth/issues/275
|
||||
"""
|
||||
return get_user_model().objects.none()
|
||||
|
||||
|
@ -163,6 +223,7 @@ class PasswordResetView(GenericAPIView):
|
|||
"""
|
||||
serializer_class = PasswordResetSerializer
|
||||
permission_classes = (AllowAny,)
|
||||
throttle_scope = 'dj_rest_auth'
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
# Create a serializer with request.data
|
||||
|
@ -188,6 +249,7 @@ class PasswordResetConfirmView(GenericAPIView):
|
|||
"""
|
||||
serializer_class = PasswordResetConfirmSerializer
|
||||
permission_classes = (AllowAny,)
|
||||
throttle_scope = 'dj_rest_auth'
|
||||
|
||||
@sensitive_post_parameters_m
|
||||
def dispatch(self, *args, **kwargs):
|
||||
|
@ -211,6 +273,7 @@ class PasswordChangeView(GenericAPIView):
|
|||
"""
|
||||
serializer_class = PasswordChangeSerializer
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_scope = 'dj_rest_auth'
|
||||
|
||||
@sensitive_post_parameters_m
|
||||
def dispatch(self, *args, **kwargs):
|
|
@ -85,17 +85,17 @@ qthelp:
|
|||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-rest-auth.qhcp"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/dj-rest-auth.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-rest-auth.qhc"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/dj-rest-auth.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/django-rest-auth"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-rest-auth"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/dj-rest-auth"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/dj-rest-auth"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
|
|
|
@ -4,7 +4,7 @@ API endpoints
|
|||
Basic
|
||||
-----
|
||||
|
||||
- /rest-auth/login/ (POST)
|
||||
- /dj-rest-auth/login/ (POST)
|
||||
|
||||
- username
|
||||
- email
|
||||
|
@ -12,24 +12,24 @@ Basic
|
|||
|
||||
Returns Token key
|
||||
|
||||
- /rest-auth/logout/ (POST)
|
||||
- /dj-rest-auth/logout/ (POST)
|
||||
|
||||
.. note:: ``ACCOUNT_LOGOUT_ON_GET = True`` to allow logout using GET - this is the exact same configuration from allauth. NOT recommended, see: http://django-allauth.readthedocs.io/en/latest/views.html#logout
|
||||
|
||||
- /rest-auth/password/reset/ (POST)
|
||||
- /dj-rest-auth/password/reset/ (POST)
|
||||
|
||||
- email
|
||||
|
||||
- /rest-auth/password/reset/confirm/ (POST)
|
||||
- /dj-rest-auth/password/reset/confirm/ (POST)
|
||||
|
||||
- uid
|
||||
- token
|
||||
- new_password1
|
||||
- new_password2
|
||||
|
||||
.. note:: uid and token are sent in email after calling /rest-auth/password/reset/
|
||||
.. note:: uid and token are sent in email after calling /dj-rest-auth/password/reset/
|
||||
|
||||
- /rest-auth/password/change/ (POST)
|
||||
- /dj-rest-auth/password/change/ (POST)
|
||||
|
||||
- new_password1
|
||||
- new_password2
|
||||
|
@ -38,7 +38,7 @@ Basic
|
|||
.. note:: ``OLD_PASSWORD_FIELD_ENABLED = True`` to use old_password.
|
||||
.. note:: ``LOGOUT_ON_PASSWORD_CHANGE = False`` to keep the user logged in after password change
|
||||
|
||||
- /rest-auth/user/ (GET, PUT, PATCH)
|
||||
- /dj-rest-auth/user/ (GET, PUT, PATCH)
|
||||
|
||||
- username
|
||||
- first_name
|
||||
|
@ -47,34 +47,58 @@ Basic
|
|||
Returns pk, username, email, first_name, last_name
|
||||
|
||||
|
||||
- /dj-rest-auth/token/verify/ (POST)
|
||||
|
||||
- token
|
||||
|
||||
Returns an empty JSON object.
|
||||
|
||||
.. note:: ``REST_USE_JWT = True`` to use token/verify/ route.
|
||||
.. note:: Takes a token and indicates if it is valid. This view provides no information about a token's fitness for a particular use. Will return a ``HTTP 200 OK`` in case of a valid token and ``HTTP 401 Unauthorized`` with ``{"detail": "Token is invalid or expired", "code": "token_not_valid"}`` in case of a invalid or expired token.
|
||||
|
||||
|
||||
- /dj-rest-auth/token/refresh/ (POST) (`see also <https://django-rest-framework-simplejwt.readthedocs.io/en/latest/getting_started.html#usage>`_)
|
||||
|
||||
- refresh
|
||||
|
||||
Returns access
|
||||
|
||||
.. note:: ``REST_USE_JWT = True`` to use token/refresh/ route.
|
||||
.. note:: Takes a refresh type JSON web token and returns an access type JSON web token if the refresh token is valid. ``HTTP 401 Unauthorized`` with ``{"detail": "Token is invalid or expired", "code": "token_not_valid"}`` in case of a invalid or expired token.
|
||||
|
||||
Registration
|
||||
------------
|
||||
|
||||
- /rest-auth/registration/ (POST)
|
||||
- /dj-rest-auth/registration/ (POST)
|
||||
|
||||
- username
|
||||
- password1
|
||||
- password2
|
||||
- email
|
||||
|
||||
- /rest-auth/registration/verify-email/ (POST)
|
||||
- /dj-rest-auth/registration/verify-email/ (POST)
|
||||
|
||||
- key
|
||||
|
||||
.. note:: If you set account email verification as mandatory, you have to add the VerifyEmailView with the used `name`.
|
||||
You need to import the view: ``from dj_rest_auth.registration.views import VerifyEmailView``. Then add the url with the corresponding name:
|
||||
``path('dj-rest-auth/account-confirm-email/', VerifyEmailView.as_view(), name='account_email_verification_sent')`` to the urlpatterns list.
|
||||
|
||||
|
||||
|
||||
Social Media Authentication
|
||||
---------------------------
|
||||
|
||||
Basing on example from installation section :doc:`Installation </installation>`
|
||||
|
||||
- /rest-auth/facebook/ (POST)
|
||||
- /dj-rest-auth/facebook/ (POST)
|
||||
|
||||
- access_token
|
||||
- code
|
||||
|
||||
.. note:: ``access_token`` OR ``code`` can be used as standalone arguments, see https://github.com/Tivix/django-rest-auth/blob/master/rest_auth/registration/views.py
|
||||
.. note:: ``access_token`` OR ``code`` can be used as standalone arguments, see https://github.com/jazzband/dj-rest-auth/blob/master/dj_rest_auth/registration/views.py
|
||||
|
||||
- /rest-auth/twitter/ (POST)
|
||||
- /dj-rest-auth/twitter/ (POST)
|
||||
|
||||
- access_token
|
||||
- token_secret
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
0.9.3
|
||||
-----
|
||||
- added social connect views
|
||||
- added check for pre-existing accounts in social login
|
||||
- prevent double-validation in LoginSerializer
|
||||
- unit tests and demo project changes for Django 2.0
|
||||
|
||||
0.9.2
|
||||
-----
|
||||
- added permission classes configuration for registration
|
||||
- added more info to JWT docs
|
||||
- added Polish translations
|
||||
|
||||
0.9.1
|
||||
-----
|
||||
- fixed import error when extending rest_auth serializers
|
||||
- added sensitive fields decorator
|
||||
- added Spanish translations
|
||||
|
||||
0.9.0
|
||||
-----
|
||||
- allowed using custom UserDetailsSerializer with JWTSerializer
|
||||
- fixed error with logout on GET
|
||||
- updated api endpoints and configuration docs
|
||||
- bugfixes
|
||||
- minor text fixes
|
||||
|
||||
0.8.2
|
||||
-----
|
||||
- fixed allauth import error
|
||||
- added swagger docs to demo project
|
||||
|
||||
0.8.1
|
||||
-----
|
||||
- added support for django-allauth hmac email confirmation pattern
|
||||
|
||||
0.8.0
|
||||
-----
|
||||
- added support for django-rest-framework-jwt
|
||||
- bugfixes
|
||||
|
||||
0.7.0
|
||||
-----
|
||||
- Wrapped API returned strings in ugettext_lazy
|
||||
- Fixed not using ``get_username`` which caused issues when using custom user model without username field
|
||||
- Django 1.9 support
|
||||
- Added ``TwitterLoginSerializer``
|
||||
|
||||
0.6.0
|
||||
-----
|
||||
- dropped support for Python 2.6
|
||||
- dropped support for Django 1.6
|
||||
- fixed demo code
|
||||
- added better validation support for serializers
|
||||
- added optional logout after password change
|
||||
- compatibility fixes
|
||||
- bugfixes
|
||||
|
||||
0.5.0
|
||||
-----
|
||||
- replaced request.DATA with request.data for compatibility with DRF 3.2
|
||||
- authorization codes for social login
|
||||
- view classes rename (appended "View" to all of them)
|
||||
- bugfixes
|
||||
|
||||
0.4.0
|
||||
-----
|
||||
- Django 1.8 compatiblity fixes
|
||||
|
||||
0.3.4
|
||||
-----
|
||||
- fixed bug in PasswordResetConfirmation serializer (token field wasn't validated)
|
||||
- fixed bug in Register view
|
||||
|
||||
0.3.3
|
||||
-----
|
||||
|
||||
- support django-rest-framework v3.0
|
||||
|
||||
0.3.2
|
||||
-----
|
||||
|
||||
- fixed few minor bugs
|
||||
|
||||
0.3.1
|
||||
-----
|
||||
|
||||
- added old_password field in PasswordChangeSerializer
|
||||
- make all endpoints browsable
|
||||
- removed LoggedInRESTAPIView, LoggedOutRESTAPIView
|
||||
- fixed minor bugs
|
||||
|
||||
0.3.0
|
||||
-----
|
||||
|
||||
- replaced ``django-registration`` with ``django-allauth``
|
||||
- moved registration logic to separated django application (``rest_auth.registration``)
|
||||
- added serializers customization in django settings
|
||||
- added social media authentication view
|
||||
- changed request method from GET to POST in logout endpoint
|
||||
- changed request method from POST to PUT/PATCH for user details edition
|
||||
- changed password reset confim url - uid and token should be sent in POST
|
||||
- increase test coverage
|
||||
- made compatibile with django 1.7
|
||||
- removed user profile support
|
30
docs/conf.py
30
docs/conf.py
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# django-rest-auth documentation build configuration file, created by
|
||||
# dj-rest-auth documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Oct 8 15:59:37 2014.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
|
@ -12,8 +12,12 @@
|
|||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import sys
|
||||
|
||||
about = {}
|
||||
with open('../dj_rest_auth/__version__.py', 'r', encoding="utf8") as f:
|
||||
exec(f.read(), about)
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
|
@ -43,17 +47,17 @@ source_suffix = '.rst'
|
|||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'django-rest-auth'
|
||||
copyright = u'2014, Tivix Inc.'
|
||||
project = u'dj-rest-auth'
|
||||
copyright = u'2020, @iMerica'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.3.0'
|
||||
version = about['__version__']
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.3.0'
|
||||
release = about['__version__']
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@ -176,7 +180,7 @@ html_static_path = ['_static']
|
|||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'django-rest-authdoc'
|
||||
htmlhelp_basename = 'dj-rest-authdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
@ -196,8 +200,8 @@ latex_elements = {
|
|||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'django-rest-auth.tex', u'django-rest-auth Documentation',
|
||||
u'Tivix Inc.', 'manual'),
|
||||
('index', 'dj-rest-auth.tex', u'dj-rest-auth Documentation',
|
||||
u'iMerica Inc.', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
@ -226,8 +230,8 @@ latex_documents = [
|
|||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'django-rest-auth', u'django-rest-auth Documentation',
|
||||
[u'Tivix Inc.'], 1)
|
||||
('index', 'dj-rest-auth', u'dj-rest-auth Documentation',
|
||||
[u'@iMerica'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
|
@ -240,8 +244,8 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'django-rest-auth', u'django-rest-auth Documentation',
|
||||
u'Tivix Inc.', 'django-rest-auth', 'One line description of project.',
|
||||
('index', 'dj-rest-auth', u'dj-rest-auth Documentation',
|
||||
u'@iMerica', 'dj-rest-auth', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
|
|
@ -6,19 +6,21 @@ Configuration
|
|||
You can define your custom serializers for each endpoint without overriding urls and views by adding ``REST_AUTH_SERIALIZERS`` dictionary in your django settings.
|
||||
Possible key values:
|
||||
|
||||
- LOGIN_SERIALIZER - serializer class in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.LoginSerializer``
|
||||
- LOGIN_SERIALIZER - serializer class in ``dj_rest_auth.views.LoginView``, default value ``dj_rest_auth.serializers.LoginSerializer``
|
||||
|
||||
- TOKEN_SERIALIZER - response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.TokenSerializer``
|
||||
- TOKEN_SERIALIZER - response for successful authentication in ``dj_rest_auth.views.LoginView``, default value ``dj_rest_auth.serializers.TokenSerializer``
|
||||
|
||||
- JWT_SERIALIZER - (Using REST_USE_JWT=True) response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.JWTSerializer``
|
||||
- JWT_SERIALIZER - (Using REST_USE_JWT=True) response for successful authentication in ``dj_rest_auth.views.LoginView``, default value ``dj_rest_auth.serializers.JWTSerializer``
|
||||
|
||||
- USER_DETAILS_SERIALIZER - serializer class in ``rest_auth.views.UserDetailsView``, default value ``rest_auth.serializers.UserDetailsSerializer``
|
||||
- JWT_TOKEN_CLAIMS_SERIALIZER - A custom JWT Claim serializer. Default is ``rest_framework_simplejwt.serializers.TokenObtainPairSerializer``
|
||||
|
||||
- PASSWORD_RESET_SERIALIZER - serializer class in ``rest_auth.views.PasswordResetView``, default value ``rest_auth.serializers.PasswordResetSerializer``
|
||||
- USER_DETAILS_SERIALIZER - serializer class in ``dj_rest_auth.views.UserDetailsView``, default value ``dj_rest_auth.serializers.UserDetailsSerializer``
|
||||
|
||||
- PASSWORD_RESET_CONFIRM_SERIALIZER - serializer class in ``rest_auth.views.PasswordResetConfirmView``, default value ``rest_auth.serializers.PasswordResetConfirmSerializer``
|
||||
- PASSWORD_RESET_SERIALIZER - serializer class in ``dj_rest_auth.views.PasswordResetView``, default value ``dj_rest_auth.serializers.PasswordResetSerializer``
|
||||
|
||||
- PASSWORD_CHANGE_SERIALIZER - serializer class in ``rest_auth.views.PasswordChangeView``, default value ``rest_auth.serializers.PasswordChangeSerializer``
|
||||
- PASSWORD_RESET_CONFIRM_SERIALIZER - serializer class in ``dj_rest_auth.views.PasswordResetConfirmView``, default value ``dj_rest_auth.serializers.PasswordResetConfirmSerializer``
|
||||
|
||||
- PASSWORD_CHANGE_SERIALIZER - serializer class in ``dj_rest_auth.views.PasswordChangeView``, default value ``dj_rest_auth.serializers.PasswordChangeSerializer``
|
||||
|
||||
|
||||
Example configuration:
|
||||
|
@ -36,18 +38,29 @@ Configuration
|
|||
You can define your custom serializers for registration endpoint.
|
||||
Possible key values:
|
||||
|
||||
- REGISTER_SERIALIZER - serializer class in ``rest_auth.registration.views.RegisterView``, default value ``rest_auth.registration.serializers.RegisterSerializer``
|
||||
- REGISTER_SERIALIZER - serializer class in ``dj_rest_auth.registration.views.RegisterView``, default value ``dj_rest_auth.registration.serializers.RegisterSerializer``
|
||||
|
||||
.. note:: The custom REGISTER_SERIALIZER must define a ``def save(self, request)`` method that returns a user model instance
|
||||
- **REST_AUTH_REGISTER_PERMISSION_CLASSES** - A tuple contains paths of another permission classes you wish to be used in ``RegisterView``, ``AllowAny`` is included by default.
|
||||
|
||||
- **REST_AUTH_TOKEN_MODEL** - model class for tokens, default value ``rest_framework.authtoken.models``
|
||||
Example :
|
||||
|
||||
- **REST_AUTH_TOKEN_CREATOR** - callable to create tokens, default value ``rest_auth.utils.default_create_token``.
|
||||
.. code-block:: python
|
||||
|
||||
REST_AUTH_REGISTER_PERMISSION_CLASSES = (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
'path.to.another.permission.class',
|
||||
...
|
||||
)
|
||||
- **REST_AUTH_TOKEN_MODEL** - path to model class for tokens, default value ``'rest_framework.authtoken.models.Token'``
|
||||
- **REST_AUTH_TOKEN_CREATOR** - path to callable or callable for creating tokens, default value ``dj_rest_auth.utils.default_create_token``.
|
||||
- **REST_SESSION_LOGIN** - Enable session login in Login API view (default: True)
|
||||
|
||||
- **REST_USE_JWT** - Enable JWT Authentication instead of Token/Session based. This is built on top of django-rest-framework-jwt http://getblimp.github.io/django-rest-framework-jwt/, which must also be installed. (default: False)
|
||||
|
||||
- **REST_USE_JWT** - Enable JWT Authentication instead of Token/Session based. This is built on top of djangorestframework-simplejwt https://github.com/SimpleJWT/django-rest-framework-simplejwt, which must also be installed. (default: False)
|
||||
- **JWT_AUTH_COOKIE** - The cookie name/key.
|
||||
- **JWT_AUTH_SECURE** - If you want the cookie to be only sent to the server when a request is made with the https scheme (default: False).
|
||||
- **JWT_AUTH_HTTPONLY** - If you want to prevent client-side JavaScript from having access to the cookie (default: True).
|
||||
- **JWT_AUTH_SAMESITE** - To tell the browser not to send this cookie when performing a cross-origin request (default: 'Lax'). SameSite isn’t supported by all browsers.
|
||||
- **OLD_PASSWORD_FIELD_ENABLED** - set it to True if you want to have old password verification on password change enpoint (default: False)
|
||||
|
||||
- **LOGOUT_ON_PASSWORD_CHANGE** - set to False if you want to keep the current user logged in after a password change
|
||||
- **JWT_AUTH_COOKIE_USE_CSRF** - Enables CSRF checks for only authenticated views when using the JWT cookie for auth. Does not effect a client's ability to authenticate using a JWT Bearer Auth header without a CSRF token.
|
||||
- **JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED** - Enables CSRF checks for authenticated and unauthenticated views when using the JWT cookie for auth. It does not effect a client's ability to authenticate using a JWT Bearer Auth header without a CSRF token (though getting the JWT token in the first place without passing a CSRF token isnt possible).
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
Demo project
|
||||
============
|
||||
|
||||
The idea of creating demo project was to show how you can potentially use
|
||||
django-rest-auth app with jQuery on frontend.
|
||||
Do these steps to make it running (ideally in virtualenv).
|
||||
This demo project shows how you can potentially use
|
||||
dj-rest-auth app with jQuery on frontend.
|
||||
To run this locally follow the steps below.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
cd /tmp
|
||||
git clone https://github.com/Tivix/django-rest-auth.git
|
||||
cd django-rest-auth/demo/
|
||||
git clone https://github.com/jazzband/dj-rest-auth.git
|
||||
cd dj-rest-auth/demo/
|
||||
pip install -r requirements.pip
|
||||
python manage.py migrate --settings=demo.settings --noinput
|
||||
python manage.py runserver --settings=demo.settings
|
||||
|
||||
Now, go to ``http://127.0.0.1:8000/`` in your browser.
|
||||
|
||||
Now, go to ``http://127.0.0.1:8000/`` in your browser. There is also a
|
||||
Single Page Application (SPA) in React within the ``demo/`` directory. To run this do:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
cd react-spa/
|
||||
yarn # or npm install
|
||||
yarn run start
|
||||
|
||||
|
||||
Now, go to ``https://localhost:3000`` in your browser to view it.
|
||||
|
|
15
docs/disclosure.rst
Normal file
15
docs/disclosure.rst
Normal file
|
@ -0,0 +1,15 @@
|
|||
Vulnerability Disclosure Policy
|
||||
===============================
|
||||
|
||||
Please observe the standard best practices of responsible disclosure, especially considering that this is OSS.
|
||||
See OWASP's disclosure `cheat sheet <https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html>`_.
|
||||
|
||||
Some basic rules:
|
||||
|
||||
- Keep it legal.
|
||||
- Respect everyone's privacy.
|
||||
- Contact the core maintainer(s) immediately if you discover a serious security vulnerability (imichael@pm.me for now).
|
||||
|
||||
|
||||
|
||||
|
|
@ -3,7 +3,7 @@ FAQ
|
|||
|
||||
1. Why account_confirm_email url is defined but it is not usable?
|
||||
|
||||
In /rest_auth/registration/urls.py we can find something like this:
|
||||
In /dj_rest_auth/registration/urls.py we can find something like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -36,12 +36,12 @@ FAQ
|
|||
# custom fields for user
|
||||
company_name = models.CharField(max_length=100)
|
||||
|
||||
To allow update user details within one request send to rest_auth.views.UserDetailsView view, create serializer like this:
|
||||
To allow update user details within one request send to dj_rest_auth.views.UserDetailsView view, create serializer like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_auth.serializers import UserDetailsSerializer
|
||||
from dj_rest_auth.serializers import UserDetailsSerializer
|
||||
|
||||
class UserSerializer(UserDetailsSerializer):
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
.. django-rest-auth documentation master file, created by
|
||||
.. dj-rest-auth documentation master file, created by
|
||||
sphinx-quickstart on Wed Oct 8 15:59:37 2014.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to django-rest-auth's documentation!
|
||||
Welcome to dj-rest-auth's documentation!
|
||||
============================================
|
||||
|
||||
.. warning:: Updating django-rest-auth from version **0.3.3** is highly recommended because of a security issue in PasswordResetConfirmation validation method.
|
||||
|
||||
.. note:: django-rest-auth from v0.3.3 supports django-rest-framework v3.0
|
||||
.. note:: dj-rest-auth version 1.0.0 now uses Django Simple JWT.
|
||||
|
||||
|
||||
|build-status| |coverage-status| |requirements-status| |docs|
|
||||
.. image:: https://circleci.com/gh/jazzband/dj-rest-auth.svg?style=svg
|
||||
:target: https://circleci.com/gh/jazzband/dj-rest-auth
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
@ -25,28 +25,4 @@ Contents
|
|||
Configuration <configuration>
|
||||
Demo project <demo>
|
||||
FAQ <faq>
|
||||
Changelog <changelog>
|
||||
|
||||
|
||||
.. |build-status| image:: https://travis-ci.org/Tivix/django-rest-auth.svg?branch=master
|
||||
:alt: build status
|
||||
:scale: 100%
|
||||
:target: https://travis-ci.org/Tivix/django-rest-auth
|
||||
|
||||
|
||||
.. |coverage-status| image:: https://coveralls.io/repos/Tivix/django-rest-auth/badge.png?branch=master
|
||||
:alt: coverage status
|
||||
:scale: 100%
|
||||
:target: https://coveralls.io/r/Tivix/django-rest-auth?branch=master
|
||||
|
||||
|
||||
.. |requirements-status| image:: https://requires.io/github/Tivix/django-rest-auth/requirements.png?branch=master
|
||||
:alt: requirements status
|
||||
:scale: 100%
|
||||
:target: https://requires.io/github/Tivix/django-rest-auth/requirements/?branch=master
|
||||
|
||||
|
||||
.. |docs| image:: https://readthedocs.org/projects/django-rest-auth/badge/?version=latest
|
||||
:scale: 100%
|
||||
:target: https://readthedocs.org/projects/django-rest-auth/?badge=latest
|
||||
:alt: Documentation Status
|
||||
Disclosure Policy <disclosure>
|
||||
|
|
|
@ -5,9 +5,9 @@ Installation
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
pip install django-rest-auth
|
||||
pip install dj-rest-auth
|
||||
|
||||
2. Add ``rest_auth`` app to INSTALLED_APPS in your django settings.py:
|
||||
2. Add ``dj_rest_auth`` app to INSTALLED_APPS in your django settings.py:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -16,37 +16,37 @@ Installation
|
|||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
...,
|
||||
'rest_auth'
|
||||
'dj_rest_auth'
|
||||
)
|
||||
|
||||
|
||||
.. note:: This project depends on ``django-rest-framework`` library, so install it if you haven't done yet. Make sure also you have installed ``rest_framework`` and ``rest_framework.authtoken`` apps
|
||||
|
||||
3. Add rest_auth urls:
|
||||
3. Add dj_rest_auth urls:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
urlpatterns = [
|
||||
...,
|
||||
url(r'^rest-auth/', include('rest_auth.urls'))
|
||||
path('dj-rest-auth/', include('dj_rest_auth.urls'))
|
||||
]
|
||||
|
||||
4. Migrate your database
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
python manage.py migrate
|
||||
|
||||
|
||||
python manage.py migrate
|
||||
|
||||
|
||||
You're good to go now!
|
||||
|
||||
|
||||
Registration (optional)
|
||||
-----------------------
|
||||
|
||||
1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install django-rest-auth[with_social]``.
|
||||
1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install 'dj-rest-auth[with_social]'``.
|
||||
|
||||
2. Add ``django.contrib.sites``, ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py:
|
||||
2. Add ``django.contrib.sites``, ``allauth``, ``allauth.account``, ``allauth.socialaccount`` and ``dj_rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py:
|
||||
|
||||
3. Add ``SITE_ID = 1`` to your django settings.py
|
||||
|
||||
|
@ -57,26 +57,27 @@ Registration (optional)
|
|||
'django.contrib.sites',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'rest_auth.registration',
|
||||
'allauth.socialaccount',
|
||||
'dj_rest_auth.registration',
|
||||
)
|
||||
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
3. Add rest_auth.registration urls:
|
||||
3. Add dj_rest_auth.registration urls:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
urlpatterns = [
|
||||
...,
|
||||
url(r'^rest-auth/', include('rest_auth.urls')),
|
||||
url(r'^rest-auth/registration/', include('rest_auth.registration.urls'))
|
||||
path('dj-rest-auth/', include('dj_rest_auth.urls')),
|
||||
path('dj-rest-auth/registration/', include('dj_rest_auth.registration.urls'))
|
||||
]
|
||||
|
||||
|
||||
Social Authentication (optional)
|
||||
--------------------------------
|
||||
|
||||
Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creating social media authentication view.
|
||||
Using ``django-allauth``, ``dj-rest-auth`` provides helpful class for creating social media authentication view.
|
||||
|
||||
.. note:: Points 1 and 2 are related to ``django-allauth`` configuration, so if you have already configured social authentication, then please go to step 3. See ``django-allauth`` documentation for more details.
|
||||
|
||||
|
@ -88,12 +89,12 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati
|
|||
...,
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'rest_auth'
|
||||
'dj_rest_auth'
|
||||
...,
|
||||
'django.contrib.sites',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'rest_auth.registration',
|
||||
'dj_rest_auth.registration',
|
||||
...,
|
||||
'allauth.socialaccount',
|
||||
'allauth.socialaccount.providers.facebook',
|
||||
|
@ -106,12 +107,12 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati
|
|||
Facebook
|
||||
########
|
||||
|
||||
3. Create new view as a subclass of ``rest_auth.registration.views.SocialLoginView`` with ``FacebookOAuth2Adapter`` adapter as an attribute:
|
||||
3. Create new view as a subclass of ``dj_rest_auth.registration.views.SocialLoginView`` with ``FacebookOAuth2Adapter`` adapter as an attribute:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
||||
from rest_auth.registration.views import SocialLoginView
|
||||
from dj_rest_auth.registration.views import SocialLoginView
|
||||
|
||||
class FacebookLogin(SocialLoginView):
|
||||
adapter_class = FacebookOAuth2Adapter
|
||||
|
@ -122,7 +123,7 @@ Facebook
|
|||
|
||||
urlpatterns += [
|
||||
...,
|
||||
url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login')
|
||||
path('dj-rest-auth/facebook/', FacebookLogin.as_view(), name='fb_login')
|
||||
]
|
||||
|
||||
|
||||
|
@ -131,13 +132,13 @@ Twitter
|
|||
|
||||
If you are using Twitter for your social authentication, it is a bit different since Twitter uses OAuth 1.0.
|
||||
|
||||
3. Create new view as a subclass of ``rest_auth.views.LoginView`` with ``TwitterOAuthAdapter`` adapter and ``TwitterLoginSerializer`` as an attribute:
|
||||
3. Create new view as a subclass of ``dj_rest_auth.registration.views.SocialLoginView`` with ``TwitterOAuthAdapter`` adapter and ``TwitterLoginSerializer`` as an attribute:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
|
||||
from rest_auth.registration.views import SocialLoginView
|
||||
from rest_auth.social_serializers import TwitterLoginSerializer
|
||||
from dj_rest_auth.registration.views import SocialLoginView
|
||||
from dj_rest_auth.social_serializers import TwitterLoginSerializer
|
||||
|
||||
class TwitterLogin(SocialLoginView):
|
||||
serializer_class = TwitterLoginSerializer
|
||||
|
@ -149,11 +150,39 @@ If you are using Twitter for your social authentication, it is a bit different s
|
|||
|
||||
urlpatterns += [
|
||||
...,
|
||||
url(r'^rest-auth/twitter/$', TwitterLogin.as_view(), name='twitter_login')
|
||||
path('dj-rest-auth/twitter/', TwitterLogin.as_view(), name='twitter_login')
|
||||
]
|
||||
|
||||
.. note:: Starting from v0.21.0, django-allauth has dropped support for context processors. Check out http://django-allauth.readthedocs.org/en/latest/changelog.html#from-0-21-0 for more details.
|
||||
|
||||
|
||||
GitHub
|
||||
######
|
||||
|
||||
If you are using GitHub for your social authentication, it uses code and not AccessToken directly.
|
||||
|
||||
3. Create new view as a subclass of ``dj_rest_auth.views.SocialLoginView`` with ``GitHubOAuth2Adapter`` adapter, an ``OAuth2Client`` and a callback_url as attributes:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
|
||||
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
|
||||
from dj_rest_auth.registration.views import SocialLoginView
|
||||
|
||||
class GithubLogin(SocialLoginView):
|
||||
adapter_class = GitHubOAuth2Adapter
|
||||
callback_url = CALLBACK_URL_YOU_SET_ON_GITHUB
|
||||
client_class = OAuth2Client
|
||||
|
||||
4. Create url for GitHubLogin view:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
urlpatterns += [
|
||||
...,
|
||||
path('dj-rest-auth/github/', GitHubLogin.as_view(), name='github_login')
|
||||
]
|
||||
|
||||
Additional Social Connect Views
|
||||
###############################
|
||||
|
||||
|
@ -162,8 +191,11 @@ If you want to allow connecting existing accounts in addition to login, you can
|
|||
.. code-block:: python
|
||||
|
||||
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
||||
from rest_auth.registration.views import SocialConnectView
|
||||
from rest_auth.social_serializers import TwitterConnectSerializer
|
||||
from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
|
||||
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
|
||||
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
|
||||
from dj_rest_auth.registration.views import SocialConnectView
|
||||
from dj_rest_auth.social_serializers import TwitterConnectSerializer
|
||||
|
||||
class FacebookConnect(SocialConnectView):
|
||||
adapter_class = FacebookOAuth2Adapter
|
||||
|
@ -172,51 +204,84 @@ If you want to allow connecting existing accounts in addition to login, you can
|
|||
serializer_class = TwitterConnectSerializer
|
||||
adapter_class = TwitterOAuthAdapter
|
||||
|
||||
class GithubConnect(SocialConnectView):
|
||||
adapter_class = GitHubOAuth2Adapter
|
||||
callback_url = CALLBACK_URL_YOU_SET_ON_GITHUB
|
||||
client_class = OAuth2Client
|
||||
|
||||
|
||||
In urls.py:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
urlpatterns += [
|
||||
...,
|
||||
url(r'^rest-auth/facebook/connect/$', FacebookConnect.as_view(), name='fb_connect')
|
||||
url(r'^rest-auth/twitter/connect/$', TwitterConnect.as_view(), name='twitter_connect')
|
||||
path('dj-rest-auth/facebook/connect/', FacebookConnect.as_view(), name='fb_connect')
|
||||
path('dj-rest-auth/twitter/connect/', TwitterConnect.as_view(), name='twitter_connect')
|
||||
path('dj-rest-auth/github/connect/', GithubConnect.as_view(), name='github_connect')
|
||||
]
|
||||
|
||||
You can also use the following views to check all social accounts attached to the current authenticated user and disconnect selected social accounts:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from rest_auth.registration.views import (
|
||||
|
||||
from dj_rest_auth.registration.views import (
|
||||
SocialAccountListView, SocialAccountDisconnectView
|
||||
)
|
||||
|
||||
urlpatterns += [
|
||||
...,
|
||||
url(
|
||||
r'^socialaccounts/$',
|
||||
path(
|
||||
'socialaccounts/',
|
||||
SocialAccountListView.as_view(),
|
||||
name='social_account_list'
|
||||
),
|
||||
url(
|
||||
r'^socialaccounts/(?P<pk>\d+)/disconnect/$',
|
||||
path(
|
||||
'socialaccounts/<int:pk>/disconnect/',
|
||||
SocialAccountDisconnectView.as_view(),
|
||||
name='social_account_disconnect'
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
JWT Support (optional)
|
||||
----------------------
|
||||
JSON Web Token (JWT) Support (optional)
|
||||
---------------------------------------
|
||||
|
||||
By default ``django-rest-auth`` uses Django's Token-based authentication. If you want to use JWT authentication, follow these steps:
|
||||
By default ``dj-rest-auth`` uses Django's Token-based authentication. If you want to use JWT authentication, follow these steps:
|
||||
|
||||
1. Install `django-rest-framework-jwt <http://getblimp.github.io/django-rest-framework-jwt/>`_
|
||||
- ``django-rest-framework-jwt`` is currently the only supported JWT library.
|
||||
2. The ``JWT_PAYLOAD_HANDLER`` and ``JWT_ENCODE_HANDLER`` settings are imported from the ``django-rest-framework-jwt`` settings object.
|
||||
- Refer to `the library's documentation <http://getblimp.github.io/django-rest-framework-jwt/#additional-settings>`_ for information on using different encoders.
|
||||
1. Install `djangorestframework-simplejwt <https://github.com/SimpleJWT/django-rest-framework-simplejwt/>`_
|
||||
- ``djangorestframework-simplejwt`` is currently the only supported JWT library.
|
||||
|
||||
3. Add the following configuration value to your settings file to enable JWT authentication.
|
||||
2. Add a simple_jwt auth configuration to the list of authentication classes.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
...
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
...
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
|
||||
)
|
||||
...
|
||||
}
|
||||
|
||||
3. Add the following configuration value to your settings file to enable JWT authentication in dj-rest-auth.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
REST_USE_JWT = True
|
||||
|
||||
4. Declare what you want the cookie key to be called.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
JWT_AUTH_COOKIE = 'my-app-auth'
|
||||
|
||||
|
||||
This example value above will cause dj-rest-auth to return a `Set-Cookie` header that looks like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Set-Cookie: my-app-auth=xxxxxxxxxxxxx; expires=Sat, 28 Mar 2020 18:59:00 GMT; HttpOnly; Max-Age=300; Path=/
|
||||
|
||||
``JWT_AUTH_COOKIE`` is also used while authenticating each request against protected views.
|
||||
|
|
|
@ -2,7 +2,7 @@ Introduction
|
|||
============
|
||||
|
||||
|
||||
Since the introduction of django-rest-framework, Django apps have been able to serve up app-level REST API endpoints. As a result, we saw a lot of instances where developers implemented their own REST registration API endpoints here and there, snippets, and so on. We aim to solve this demand by providing django-rest-auth, a set of REST API endpoints to handle User Registration and Authentication tasks. By having these API endpoints, your client apps such as AngularJS, iOS, Android, and others can communicate to your Django backend site independently via REST APIs for User Management. Of course, we'll add more API endpoints as we see the demand.
|
||||
Since the introduction of django-rest-framework, Django apps have been able to serve up app-level REST API endpoints. As a result, we saw a lot of instances where developers implemented their own REST registration API endpoints here and there, snippets, and so on. We aim to solve this demand by providing dj-rest-auth, a set of REST API endpoints to handle User Registration and Authentication tasks. By having these API endpoints, your client apps such as AngularJS, iOS, Android, and others can communicate to your Django backend site independently via REST APIs for User Management. Of course, we'll add more API endpoints as we see the demand.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
@ -18,17 +18,13 @@ Features
|
|||
Apps structure
|
||||
--------------
|
||||
|
||||
* ``rest_auth`` has basic auth functionality like login, logout, password reset and password change
|
||||
* ``rest_auth.registration`` has logic related with registration and social media authentication
|
||||
* ``dj_rest_auth`` has basic auth functionality like login, logout, password reset and password change
|
||||
* ``dj_rest_auth.registration`` has logic related with registration and social media authentication
|
||||
|
||||
|
||||
Angular app
|
||||
-----------
|
||||
|
||||
- Tivix has also created angular module which uses API endpoints from this app - `angular-django-registration-auth <https://github.com/Tivix/angular-django-registration-auth>`_
|
||||
|
||||
|
||||
Demo project
|
||||
Demo projects
|
||||
------------
|
||||
|
||||
- You can also check our :doc:`Demo Project </demo>` which is using jQuery on frontend.
|
||||
- There is also a React demo project based on Create-React-App in demo/react-spa/
|
||||
|
|
|
@ -115,9 +115,9 @@ if "%1" == "qthelp" (
|
|||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-rest-auth.qhcp
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\dj-rest-auth.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-rest-auth.ghc
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\dj-rest-auth.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
|
|
4
flake8
4
flake8
|
@ -1,4 +0,0 @@
|
|||
[flake8]
|
||||
max-line-length = 120
|
||||
exclude = docs/*,demo/*
|
||||
ignore = F403
|
|
@ -1,51 +0,0 @@
|
|||
from django.conf import settings
|
||||
|
||||
from rest_auth.serializers import (
|
||||
TokenSerializer as DefaultTokenSerializer,
|
||||
JWTSerializer as DefaultJWTSerializer,
|
||||
UserDetailsSerializer as DefaultUserDetailsSerializer,
|
||||
LoginSerializer as DefaultLoginSerializer,
|
||||
PasswordResetSerializer as DefaultPasswordResetSerializer,
|
||||
PasswordResetConfirmSerializer as DefaultPasswordResetConfirmSerializer,
|
||||
PasswordChangeSerializer as DefaultPasswordChangeSerializer)
|
||||
from .utils import import_callable, default_create_token
|
||||
|
||||
create_token = import_callable(
|
||||
getattr(settings, 'REST_AUTH_TOKEN_CREATOR', default_create_token))
|
||||
|
||||
serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {})
|
||||
|
||||
TokenSerializer = import_callable(
|
||||
serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer))
|
||||
|
||||
JWTSerializer = import_callable(
|
||||
serializers.get('JWT_SERIALIZER', DefaultJWTSerializer))
|
||||
|
||||
UserDetailsSerializer = import_callable(
|
||||
serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer)
|
||||
)
|
||||
|
||||
LoginSerializer = import_callable(
|
||||
serializers.get('LOGIN_SERIALIZER', DefaultLoginSerializer)
|
||||
)
|
||||
|
||||
PasswordResetSerializer = import_callable(
|
||||
serializers.get(
|
||||
'PASSWORD_RESET_SERIALIZER',
|
||||
DefaultPasswordResetSerializer
|
||||
)
|
||||
)
|
||||
|
||||
PasswordResetConfirmSerializer = import_callable(
|
||||
serializers.get(
|
||||
'PASSWORD_RESET_CONFIRM_SERIALIZER',
|
||||
DefaultPasswordResetConfirmSerializer
|
||||
)
|
||||
)
|
||||
|
||||
PasswordChangeSerializer = import_callable(
|
||||
serializers.get(
|
||||
'PASSWORD_CHANGE_SERIALIZER',
|
||||
DefaultPasswordChangeSerializer
|
||||
)
|
||||
)
|
BIN
rest_auth/locale/sv/LC_MESSAGES/django.mo
Normal file
BIN
rest_auth/locale/sv/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
107
rest_auth/locale/sv/LC_MESSAGES/django.po
Normal file
107
rest_auth/locale/sv/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,107 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-05-10 12:48+0000\n"
|
||||
"PO-Revision-Date: 2020-05-10 15:04+0200\n"
|
||||
"Language: sv\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.2.4\n"
|
||||
|
||||
#: rest_auth/rest_auth/registration/serializers.py:67
|
||||
msgid "View is not defined, pass it as a context variable"
|
||||
msgstr "Vyn definieras inte, skicka den som en kontextvariabel"
|
||||
|
||||
#: rest_auth/rest_auth/registration/serializers.py:72
|
||||
msgid "Define adapter_class in view"
|
||||
msgstr "Definiera “adapter_class” i vyn"
|
||||
|
||||
#: rest_auth/rest_auth/registration/serializers.py:91
|
||||
msgid "Define callback_url in view"
|
||||
msgstr "Definiera “callback_url” i vyn"
|
||||
|
||||
#: rest_auth/rest_auth/registration/serializers.py:95
|
||||
msgid "Define client_class in view"
|
||||
msgstr "Definiera “client_class” i vyn"
|
||||
|
||||
#: rest_auth/rest_auth/registration/serializers.py:116
|
||||
msgid "Incorrect input. access_token or code is required."
|
||||
msgstr "Felaktig inmatning. access_token eller code krävs."
|
||||
|
||||
#: rest_auth/rest_auth/registration/serializers.py:125
|
||||
msgid "Incorrect value"
|
||||
msgstr "Felaktigt värde"
|
||||
|
||||
#: rest_auth/rest_auth/registration/serializers.py:139
|
||||
msgid "User is already registered with this e-mail address."
|
||||
msgstr "Användaren är redan registrerad med den här e-postadressen."
|
||||
|
||||
#: rest_auth/rest_auth/registration/serializers.py:185
|
||||
msgid "A user is already registered with this e-mail address."
|
||||
msgstr "En användare är redan registrerad med den här e-postadressen."
|
||||
|
||||
#: rest_auth/rest_auth/registration/serializers.py:193
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "De två lösenordsfälten matchade inte."
|
||||
|
||||
#: rest_auth/rest_auth/registration/views.py:51
|
||||
msgid "Verification e-mail sent."
|
||||
msgstr "Verifikationsmail har skickats."
|
||||
|
||||
#: rest_auth/rest_auth/registration/views.py:98
|
||||
msgid "ok"
|
||||
msgstr "ok"
|
||||
|
||||
#: rest_auth/rest_auth/serializers.py:33
|
||||
msgid "Must include \"email\" and \"password\"."
|
||||
msgstr "Måste inkludera “email” och “password”."
|
||||
|
||||
#: rest_auth/rest_auth/serializers.py:44
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "Måste innehålla “username” och “password”."
|
||||
|
||||
#: rest_auth/rest_auth/serializers.py:57
|
||||
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||
msgstr "Måste innehålla antingen “username” eller “email” och “password”."
|
||||
|
||||
#: rest_auth/rest_auth/serializers.py:98
|
||||
msgid "User account is disabled."
|
||||
msgstr "Användarkontot är avstängt."
|
||||
|
||||
#: rest_auth/rest_auth/serializers.py:101
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "Det går inte att logga in med de angivna uppgifterna."
|
||||
|
||||
#: rest_auth/rest_auth/serializers.py:110
|
||||
msgid "E-mail is not verified."
|
||||
msgstr "E-post är inte verifierad."
|
||||
|
||||
#: rest_auth/rest_auth/serializers.py:259
|
||||
msgid "Your old password was entered incorrectly. Please enter it again."
|
||||
msgstr "Ditt gamla lösenord angavs felaktigt. Vänligen ange det igen."
|
||||
|
||||
#: rest_auth/rest_auth/views.py:137
|
||||
msgid "Successfully logged out."
|
||||
msgstr "Utloggad."
|
||||
|
||||
#: rest_auth/rest_auth/views.py:190
|
||||
msgid "Password reset e-mail has been sent."
|
||||
msgstr "E-post för återställning av lösenord har skickats."
|
||||
|
||||
#: rest_auth/rest_auth/views.py:216
|
||||
msgid "Password has been reset with the new password."
|
||||
msgstr "Lösenordet har återställts med det nya lösenordet."
|
||||
|
||||
#: rest_auth/rest_auth/views.py:238
|
||||
msgid "New password has been saved."
|
||||
msgstr "Nytt lösenord har sparats."
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user