Skip to content

Commit

Permalink
Add the simplest JWT auth without refreshing access token and persist…
Browse files Browse the repository at this point in the history
…ing tokens
  • Loading branch information
mrEvgenX committed May 29, 2020
1 parent 5a6c4b8 commit ae8096d
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 34 deletions.
9 changes: 8 additions & 1 deletion django/config/settings.py
Expand Up @@ -77,7 +77,14 @@
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
]
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
}

# Database
Expand Down
10 changes: 9 additions & 1 deletion django/config/urls.py
Expand Up @@ -16,9 +16,17 @@
from django.contrib import admin
from django.urls import path, include
import core.urls

from rest_framework.schemas import get_schema_view
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', get_schema_view()),
path('api/auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api/v1/auth/token/obtain/', TokenObtainPairView.as_view()),
path('api/v1/auth/token/refresh/', TokenRefreshView.as_view()),
path('api/v1/', include(core.urls, namespace='api')),
]
120 changes: 89 additions & 31 deletions react/src/App.js
@@ -1,23 +1,32 @@
import React, { Component } from 'react';
import base64 from 'base-64';
import './App.css';
import HeaderBlock from './components/HeaderBlock';
import Main from './components/Main';


function populateState() {
function populateState(accessToken) {
return new Promise((resolve, _) => {
Promise.all([
'http://localhost:8000/api/v1/folders',
'http://localhost:8000/api/v1/items',
'http://localhost:8000/api/v1/entries'
].map(url => fetch(url)))
].map(url => fetch(
url,
{
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Authorization': `Bearer ${accessToken}`
}
}
)))
.then(responses => {
Promise.all(responses.map(response => response.json()))
.then(data => {
console.log(accessToken);
console.log(data)
resolve({
folders: data[0],
trackedItems: data[1],
trackedItems: data[1],
trackEntries: data[2]
});
});
Expand All @@ -31,80 +40,129 @@ class App extends Component {
constructor(props) {
super(props);
this.state = {
// TODO store it to local storage
auth: {
refresh: undefined,
access: undefined,
},
folders: [],
trackedItems: [],
trackEntries: [],
createFolder: this.createFolder,
createElement: this.createElement,
addTrackEntry: this.addTrackEntry
addTrackEntry: this.addTrackEntry,
authenticate: this.authenticate
};
populateState().then(data => {this.setState(data)});
fetch(
'http://localhost:8000/api/v1/auth/token/refresh/',
{
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({ refresh: this.state.auth.refresh })
}
)
.then(response => {
if (!response.ok)
throw new Error(response.status);
return response;
})
.then(response => response.json())
.catch(error => {
console.log('error', error);
})
.then(data => {
console.log(data)
this.setState({ auth: { ...this.state.auth, access: data['access'] } });
populateState(this.state.auth.access).then(data => { this.setState(data) });
})
.catch(error => {
console.log(error);
})
}

authenticate = (username, password) => {
fetch(
'http://localhost:8000/api/v1/auth/token/obtain/',
{
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({username, password})
})
.then(response => {
if (!response.ok)
throw new Error(response.status);
return response;
})
.then(response => response.json())
.then(data => {
this.setState({
auth: { ...data }
});
populateState(this.state.auth.access).then(data => { this.setState(data) });
})
.catch(error => {
console.log(error);
});
}

createFolder = (name) => {
// TODO потом прикрутится авторизация и будет круто
const username = 'admin'
const password = 'password1234'
fetch(
'http://localhost:8000/api/v1/folders',
{
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Authorization': 'Basic ' + base64.encode(username + ":" + password)
'Authorization': `Bearer ${this.state.auth.access}`
},
body: JSON.stringify({name})
body: JSON.stringify({ name })
}
)
)
.then(response => response.json())
.then(data => {
this.setState({folders: [...this.state.folders, data]})
this.setState({ folders: [...this.state.folders, data] })
});
}

createElement = (name, folder) => {
// TODO потом прикрутится авторизация и будет круто
const username = 'admin'
const password = 'password1234'
fetch(
'http://localhost:8000/api/v1/items',
{
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Authorization': 'Basic ' + base64.encode(username + ":" + password)
'Authorization': `Bearer ${this.state.auth.access}`
},
body: JSON.stringify({folder, name})
body: JSON.stringify({ folder, name })
}
)
)
.then(response => response.json())
.then(data => {
this.setState({trackedItems: [...this.state.trackedItems, data]})
this.setState({ trackedItems: [...this.state.trackedItems, data] })
});
}

addTrackEntry = (itemId) => {
// TODO потом прикрутится авторизация и будет круто
const username = 'admin'
const password = 'password1234'

const now = new Date()
const month = now.getMonth()+1
const timeBucket = `${now.getFullYear()}-${month < 10? '0' + month : month}-${now.getDate()}`
const month = now.getMonth() + 1
const timeBucket = `${now.getFullYear()}-${month < 10 ? '0' + month : month}-${now.getDate()}`
fetch(
'http://localhost:8000/api/v1/entries',
{
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Authorization': 'Basic ' + base64.encode(username + ":" + password)
'Authorization': `Bearer ${this.state.auth.access}`
},
body: JSON.stringify({timeBucket, item: itemId})
body: JSON.stringify({ timeBucket, item: itemId })
}
)
)
.then(response => response.json())
.then(data => {
this.setState({trackEntries: [...this.state.trackEntries, data]})
this.setState({ trackEntries: [...this.state.trackEntries, data] })
})
.catch(error => {
console.log(error);
Expand All @@ -117,7 +175,7 @@ class App extends Component {
<HeaderBlock />
<Main globalState={this.state} />
</div>
);
);
}

}
Expand Down
3 changes: 3 additions & 0 deletions react/src/components/HeaderBlock.js
Expand Up @@ -7,6 +7,9 @@ export default function HeaderBlock() {
<>
<h1>Easy Track</h1>
<p><Link to='/'>Папки</Link></p>
<p><Link to='/login'>Вход</Link></p>
<p><Link to='/register'>Регистрация</Link></p>
<hr/>
</>
);
}
47 changes: 47 additions & 0 deletions react/src/components/Login.js
@@ -0,0 +1,47 @@
import React, { Component } from 'react';


export default class Login extends Component {

constructor(props) {
super(props)
this.state = {
login: '',
password: ''
}
}

handleLoginChange = e => {
this.setState({login: e.target.value});
}

handlePasswordChange = e => {
this.setState({password: e.target.value});
}

handleLoginClick = e => {
e.preventDefault();
const { authenticate } = this.props;
console.log(this.state.login, this.state.password);
authenticate(this.state.login, this.state.password);
}

render() {
return (
<>
<h2>Вход на сайт</h2>
<form>
<div>
<input type='text' name='login' placeholder='E-mail' onChange={this.handleLoginChange} />
</div>
<div>
<input type='password' name='password' placeholder='Пароль' onChange={this.handlePasswordChange} />
</div>
<div>
<input type='submit' value='Войти' onClick={this.handleLoginClick} />
</div>
</form>
</>
);
}
}
6 changes: 5 additions & 1 deletion react/src/components/Main.js
Expand Up @@ -3,13 +3,17 @@ import {Switch, Route} from 'react-router-dom';
import FoldersList from './FoldersList';
import ItemsList from './ItemsList';
import ItemsListStat from './ItemsListStat';
import Login from './Login';
import Register from './Register';

export default function Main(props) {
const { globalState: { folders, trackedItems, trackEntries, createFolder, createElement, addTrackEntry } } = props;
const { globalState: { folders, trackedItems, trackEntries, createFolder, createElement, addTrackEntry, authenticate } } = props;
return (
<Switch>
<Route exact path="/"
render={() => <FoldersList folders={folders} createFolder={createFolder} />} />
<Route exact path="/login" render={() => <Login authenticate={authenticate} />} />
<Route exact path="/register" component={Register} />
<Route path="/folder/:folderSlug/statistics"
render={props => <ItemsListStat {...props} folders={folders} trackedItems={trackedItems} trackEntries={trackEntries} createElement={createElement} />} />
<Route path="/folder/:folderSlug"
Expand Down
24 changes: 24 additions & 0 deletions react/src/components/Register.js
@@ -0,0 +1,24 @@
import React from 'react';


export default function Login() {
return (
<>
<h2>Вход на сайт</h2>
<form>
<div>
<input type='text' name='login' placeholder='E-mail' />
</div>
<div>
<input type='password' name='password' placeholder='Пароль' />
</div>
<div>
<input type='password' name='password_repeat' placeholder='Пароль еще раз' />
</div>
<div>
<input type='submit' value='Зарегистрироваться' />
</div>
</form>
</>
);
}
1 change: 1 addition & 0 deletions requirements.txt
Expand Up @@ -2,3 +2,4 @@ django==3.0.6
django-cors-headers==3.3.0
djangorestframework==3.11.0
unidecode==1.1.1
djangorestframework-simplejwt==4.4.0

0 comments on commit ae8096d

Please sign in to comment.