Merge branch 'pr/1'

This commit is contained in:
Slava 2020-10-08 19:14:42 +03:00
commit 759f59f1ab
109 changed files with 2992 additions and 659 deletions

47
.circleci/config.yml Normal file
View 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
View File

@ -0,0 +1,2 @@
service_name: travis-pro
repo_token: Q58WdUuZOi89XHyDeDsGE2lxUGQ2IfqP3

14
.github/workflows/main.yml vendored Normal file
View 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
View File

@ -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/

View File

@ -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.*

View File

@ -1 +1 @@
http://github.com/Tivix/django-rest-auth/contributors
http://github.com/jazzband/dj-rest-auth/contributors

3
CONTRIBUTING.md Normal file
View 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).

View File

@ -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.

View File

@ -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
View 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!

View File

@ -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

View File

@ -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

View File

@ -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'),

View File

@ -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
View 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
View 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 cant go back!**
If you arent 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 youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt 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

View 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"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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"
}

View File

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

View 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
View 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;

View 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();
});

View 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
View 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();

View 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
View 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
View 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';

View File

@ -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

View File

@ -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 -->

View File

@ -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 %}

View 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 %}

View File

@ -1,3 +0,0 @@
--editable .
responses>=0.5.0
djangorestframework-jwt

View 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'

View 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
View 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

Binary file not shown.

View 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."

Binary file not shown.

View 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” nest 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 "Ladresse email na 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 "Lemail 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"

Binary file not shown.

View 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."

Binary file not shown.

View 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 "새로운 패스워드가 저장되었습니다."

Binary file not shown.

View 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."

View 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."

View 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 "Новий пароль збережений."

Binary file not shown.

View 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 "新密码已设置成功。"

Binary file not shown.

View 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
View 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'))

View File

@ -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():

View File

@ -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:

View File

@ -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'),
]

View File

@ -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)

View File

@ -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):

View File

@ -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):

View 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/')),
]

View File

@ -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

View 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

View File

@ -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"

View File

@ -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)

View File

@ -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)

View File

@ -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
View 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
View 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

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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'),
]

View File

@ -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 isnt 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).

View File

@ -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
View 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).

View File

@ -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):

View File

@ -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>

View File

@ -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.

View File

@ -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/

View File

@ -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
View File

@ -1,4 +0,0 @@
[flake8]
max-line-length = 120
exclude = docs/*,demo/*
ignore = F403

View File

@ -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
)
)

Binary file not shown.

View 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