From 4ec4b8f026cfa58e773624fb5d0e5e919be2beff Mon Sep 17 00:00:00 2001 From: Daniel Cortes Date: Sun, 12 Jul 2020 23:07:05 -0400 Subject: [PATCH] Issue #25 Generando un challenge de verdad para oauth --- package-lock.json | 5 + package.json | 1 + src/services/auth_service.js | 215 ++++++++++++++++++----------------- 3 files changed, 119 insertions(+), 102 deletions(-) diff --git a/package-lock.json b/package-lock.json index ab5dcb0..d81699e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12402,6 +12402,11 @@ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, + "sjcl": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.8.tgz", + "integrity": "sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==" + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", diff --git a/package.json b/package.json index 6c30c4e..f5675b4 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "react-router-dom": "^5.2.0", "react-scripts": "3.4.1", "react-tabs": "^3.1.1", + "sjcl": "^1.0.8", "typescript": "^3.9.6" }, "scripts": { diff --git a/src/services/auth_service.js b/src/services/auth_service.js index 1dfea8b..795a2e3 100644 --- a/src/services/auth_service.js +++ b/src/services/auth_service.js @@ -1,142 +1,153 @@ import axios from "axios"; +import sjcl from "sjcl"; import {getUser} from "./user_service"; 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 generate_challenge = () => { + const dictionary = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; + let code = ''; + + for (let i = 0; i < 128; i++) { + let pos = Math.floor(Math.random() * dictionary.length); + code += dictionary.substring(pos, pos + 1); + } + + let challenge = sjcl.codec.base64url.fromBits(sjcl.hash.sha256.hash(code)); + + return { + code: code, + challenge: challenge, + 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()}`; + 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; + window.localStorage.setItem('code_verifier', challenge.code); + window.location.href = url; - return null; + return null; } export const do_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)); + 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 + return response.data } export const do_refresh = async (token) => { - const params = { - grant_type: 'refresh_token', - refresh_token: token, - client_id: client_id, - }; + const params = { + grant_type: 'refresh_token', + refresh_token: token, + client_id: client_id, + }; - const url = `${oauth_url}/token/` - const response = await axios.post(url, new URLSearchParams(params)); - return response.data; + const url = `${oauth_url}/token/` + const response = await axios.post(url, new URLSearchParams(params)); + return response.data; } export const auth = async (params) => { - // Como response se puede obtener desde refresh o desde un auth regular, se almacena al inicio - let response; + // Como response se puede obtener desde refresh o desde un auth regular, se almacena al inicio + let response; - // Preámbulo, puede existir un refresh_token usable asi que debe hacer el flow de renew - if (window.localStorage.getItem('refresh_token')) { - const refresh_token = window.localStorage.getItem('refresh_token'); - response = await do_refresh(refresh_token); + // Preámbulo, puede existir un refresh_token usable asi que debe hacer el flow de renew + if (window.localStorage.getItem('refresh_token')) { + const refresh_token = window.localStorage.getItem('refresh_token'); + response = await do_refresh(refresh_token); + } + + if (!response) { + // 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'}; } - if (!response) { - // 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'); - response = await do_auth(code, code_verifier) + // 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}; } - // Puede que la respuesta sea errónea por varias razones - if (response.error) { - return {status: response.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'); + response = await do_auth(code, code_verifier) + } - // 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() + // Puede que la respuesta sea errónea por varias razones + if (response.error) { + return {status: response.error} + } - const access_token = response.access_token; - const refresh = response.refresh_token; - const expires = new Date(new Date().getTime() + ((response.expires_in) * 1000)) + // 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() - window.localStorage.setItem('refresh_token', refresh); - window.localStorage.setItem('expires', expires); + const access_token = response.access_token; + const refresh = response.refresh_token; + const expires = new Date(new Date().getTime() + ((response.expires_in) * 1000)) - // Almacenar el usuario en localStorage - const user = await getUser(access_token); - window.localStorage.setItem('user', JSON.stringify(user)) + 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: access_token}; + // Almacenar el usuario en localStorage + const user = await getUser(access_token); + window.localStorage.setItem('user', JSON.stringify(user)) + + // Finalmente se retorna el access_token para ser utilizado en el estado de la app + return {status: 'done', access_token: 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' - } + // 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(); + 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)); + 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, - } + return { + access_revoke: response_access_revoke, + refresh_revoke: response_refresh_revoke, + } }