diff --git a/src/components/Auth.jsx b/src/components/Auth.jsx
new file mode 100644
index 0000000..f9b4958
--- /dev/null
+++ b/src/components/Auth.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import {Redirect} from "react-router-dom";
+import queryString from "query-string";
+
+import {auth, logout} from "../services/auth_service";
+import {useStateValue} from "../services/State";
+
+export const AuthLogin = (props) => {
+ const [context, dispatch] = useStateValue();
+
+ if (context.user.auth) {
+ // El usuario ya esta logeado, no hay nada que hacer
+ props.history.push('/')
+ }
+
+ const params = queryString.parse(props.location.search);
+ auth(params).then((response) => {
+ console.debug(response, window.localStorage);
+
+ if(response.status === 'redirect_to_code'){
+ // Se va o ya se redirigió hacia la pagina que obtiene el código, no hay nada que hacer.
+ return null;
+ } else if (response.status === 'code_error') {
+ // Hubo un error al obtener el código
+ props.history.push('/error')
+ } else if (response.status === 'done') {
+ // Termino gud, refresh_token y expires debería estar en local storage y access_token
+ // debió ser retornado con la response
+ props.history.push('/')
+ dispatch({type: 'login', user:{auth: true, access_token: response.access_token}});
+ } else {
+ // Ocurrió un error inesperado al hacer auth :c
+ props.history.push('/error')
+ }
+ })
+
+ return null;
+}
+
+export const AuthLogout = (props) => {
+ const [context, dispatch] = useStateValue();
+ if (!context.user.auth) {
+ return
+ }
+
+ logout(context.user.access_token).then(result => {
+ console.log(result);
+ });
+
+ dispatch({type: 'logout', user: {auth: false}})
+ return
+}
diff --git a/src/components/Nav.jsx b/src/components/Nav.jsx
index 283b0e0..224072c 100644
--- a/src/components/Nav.jsx
+++ b/src/components/Nav.jsx
@@ -1,13 +1,41 @@
import React from "react";
import {Link} from "react-router-dom";
import './Nav.scss';
+import {useStateValue} from '../services/State'
-export const Nav = () => (
-
-)
+export const Nav = (props) => {
+ const context = useStateValue()[0];
+
+ const showLogin = () => {
+ return context.user.auth === false;
+ }
+
+ const buttons = () => {
+ if (showLogin()) {
+ return
+ -
+ Iniciar Sesión
+
+ -
+ Registrate
+
+
+ }else {
+ return
+ -
+ Bienvenido {context.user.access_token}
+
+ -
+ Cerrar Sesión
+
+
+ }
+ }
+
+ return (
+
+ )
+}
diff --git a/src/index.jsx b/src/index.jsx
index cc17ff4..0fd8d65 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -6,6 +6,8 @@ import './styles/reset.css';
import './styles/main.scss';
import './styles/tabs.scss';
+import {StateProvider} from "./services/State";
+
import {Nav} from "./components/Nav";
import {SearchBar} from "./components/SearchBar";
import {ScrollToTopRouter} from "./components/ScrollToTop";
@@ -17,27 +19,28 @@ import {ReleaseView} from "./views/Release";
import {Recomended} from "./views/Recomended";
import {SongView} from "./views/Song";
import {Grid, RowCol} from './components/Grid';
+import {AuthLogin, AuthLogout} from "./components/Auth";
const Main = (props) => {
const navigate = (query) => props.history.push(`/search?query=${query}`);
return (
-
- Busca la musica que te gusta!
-
-
-
+
+ Busca la musica que te gusta!
+
+
+
)
}
const NoRoute = (props) => {
return (
-
- Esa pagina no existe
-
-
-
-
+
+ Esa pagina no existe
+
+
+
+
);
}
@@ -48,10 +51,15 @@ const App = () => (
+
+
+
+
+
@@ -59,7 +67,15 @@ const App = () => (
);
+const AppWithState = () => {
+ return (
+
+
+
+ );
+}
+
ReactDOM.render(
- ,
+ ,
document.getElementById('root')
);
diff --git a/src/services/State.jsx b/src/services/State.jsx
new file mode 100644
index 0000000..3f47b97
--- /dev/null
+++ b/src/services/State.jsx
@@ -0,0 +1,35 @@
+import React, {createContext, useContext, useReducer} from 'react';
+
+const initialState = {user: {auth: false}};
+
+const reducer = (state, action) => {
+ console.log(state, action);
+
+ switch (action.type) {
+ case 'login':
+ return {
+ ...state,
+ user: action.user
+ }
+ case 'logout':
+ return {
+ ...state,
+ user: action.user
+ }
+ default:
+ return state;
+ }
+};
+
+export const StateContext = createContext(null);
+
+export const StateProvider = ({children}) => {
+
+ return (
+
+ {children}
+
+ );
+}
+
+export const useStateValue = () => useContext(StateContext);
diff --git a/src/services/auth_service.js b/src/services/auth_service.js
new file mode 100644
index 0000000..816f1b3
--- /dev/null
+++ b/src/services/auth_service.js
@@ -0,0 +1,113 @@
+import axios from "axios";
+
+const current_host = `${window.location.protocol}//${window.location.host}`
+const oauth_url = `${process.env.REACT_APP_API_SERVER}/oauth`;
+const client_id = process.env["REACT_APP_CLIENT_ID"];
+
+const generate_challenge = () => {
+ return {
+ code: '5d2309e5bb73b864f989753887fe52f79ce5270395e25862da6940d5',
+ challenge: 'MChCW5vD-3h03HMGFZYskOSTir7II_MMTb8a9rJNhnI',
+ method: 'S256',
+ }
+}
+
+export const redirect_to_code = () => {
+ const challenge = generate_challenge()
+ const params = {
+ response_type: 'code',
+ client_id: client_id,
+ redirect_uri: `${current_host}/login`,
+ scope: 'read write',
+ code_challenge: challenge.challenge,
+ code_challenge_method: challenge.method
+ };
+ const url = `${oauth_url}/authorize/?${new URLSearchParams(params).toString()}`;
+
+ window.localStorage.setItem('code_verifier', challenge.code);
+ window.location.href = url;
+
+ return null;
+}
+
+export const get_auth = async (code, code_verifier) => {
+ const params = {
+ code: code,
+ code_verifier: code_verifier,
+ grant_type: 'authorization_code',
+ redirect_uri: `${current_host}/login`,
+ client_id: client_id,
+ };
+ const url = `${oauth_url}/token/`
+ const response = await axios.post(url, new URLSearchParams(params));
+
+ return response.data
+}
+
+export const auth = async (params) => {
+ // Primera fase, obtener código
+ // Si es que no se tiene se tiene que redirigir a la pagina de oauth que entregara el código
+ // a la pagina de redirect como parámetro GET, este trabajo lo hace redirect y se volverá a
+ // ejecutar este método cuando se entre a esa ruta
+ if (!window.localStorage.getItem('code_verifier')) {
+ redirect_to_code();
+ return {status: 'redirect_to_code'};
+ }
+
+ // Segunda fase, se llama a auth con los parámetros de la ruta.
+ // Estos parámetros puede contener un error, en ese caso se elimina el code_verifier del storage
+ // porque no sera util y fallo la request.
+ if (params.error) {
+ window.localStorage.clear()
+ return {status: 'code_error', value: params.error};
+ }
+
+ // Teniendo el código en los parámetros se intenta obtener el código de autorización.
+ const code = params.code;
+ const code_verifier = window.localStorage.getItem('code_verifier');
+ const response = await get_auth(code, code_verifier)
+
+ // Puede que la respuesta sea erronea por varias razones
+ if (response.error) {
+ return {status: response.error}
+ }
+
+ // Una vez se tiene la respuesta se almacena el refresh_token y el expires_in
+ // en localstorage para ser utilizado mas tarde para renovar el access_token
+ window.localStorage.clear()
+
+ const refresh = response.refresh_token;
+ const expires = new Date(new Date().getTime() + ((response.expires_in) * 1000))
+
+ window.localStorage.setItem('refresh_token', refresh);
+ window.localStorage.setItem('expires', expires);
+
+ // Finalmente se retorna el access_token para ser utilizado en el estado de la app
+ return {status: 'done', access_token: response.access_token};
+}
+
+export const logout = async (access_token) => {
+ // Para hacer logout de un usuario es necesario eliminar el refresh token de su localStorage
+ // y se llama a revocar los tokens en el servidor oauth
+ const revoke_access_params = {
+ token: access_token,
+ client_id: client_id,
+ token_type_hint: 'access_token'
+ }
+ const revoke_refresh_params = {
+ token: window.localStorage.getItem('refresh_token'),
+ client_id: client_id,
+ token_type_hint: 'refresh_token'
+ }
+
+ window.localStorage.clear();
+
+ const url = `${oauth_url}/revoke_token/`;
+ const response_access_revoke = await axios.post(url, new URLSearchParams(revoke_access_params));
+ const response_refresh_revoke = await axios.post(url, new URLSearchParams(revoke_refresh_params));
+
+ return {
+ access_revoke: response_access_revoke,
+ refresh_revoke: response_refresh_revoke,
+ }
+}