Web Client #11
@ -1,5 +1,10 @@
|
|||||||
NODE_ID=aUniqueString
|
NODE_ID=aUniqueString
|
||||||
|
|
||||||
|
REACT_APP_GITHUB_OAUTH=https://github.com/login/oauth/authorize?client_id=CLIENT_ID_HERE&redirect_uri=http://localhost:8080/oauth/redirect
|
||||||
|
CLIENT_REDIRECT_AFTER_OAUTH=http://localhost:3000/auth-successful
|
||||||
|
|
||||||
GITHUB_CLIENT_ID=
|
GITHUB_CLIENT_ID=
|
||||||
GITHUB_CLIENT_SECRET=
|
GITHUB_CLIENT_SECRET=
|
||||||
GITHUB_USER_AGENT=
|
GITHUB_USER_AGENT=
|
||||||
|
|
||||||
|
JWT_SECRET=aVerySecretJwtSigningSecret
|
@ -3,7 +3,12 @@ import Config
|
|||||||
config :chessh, Web,
|
config :chessh, Web,
|
||||||
github_client_id: System.get_env("GITHUB_CLIENT_ID"),
|
github_client_id: System.get_env("GITHUB_CLIENT_ID"),
|
||||||
github_client_secret: System.get_env("GITHUB_CLIENT_SECRET"),
|
github_client_secret: System.get_env("GITHUB_CLIENT_SECRET"),
|
||||||
github_user_agent: System.get_env("GITHUB_USER_AGENT")
|
github_user_agent: System.get_env("GITHUB_USER_AGENT"),
|
||||||
|
client_redirect_after_successful_sign_in:
|
||||||
|
System.get_env("CLIENT_REDIRECT_AFTER_OAUTH", "http://localhost:3000")
|
||||||
|
|
||||||
|
config :joken,
|
||||||
|
default_signer: System.get_env("JWT_SECRET")
|
||||||
|
|
||||||
if config_env() == :prod do
|
if config_env() == :prod do
|
||||||
database_url =
|
database_url =
|
||||||
|
104
front/src/context/auth_context.js
Normal file
104
front/src/context/auth_context.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import React, { useContext, useState, createContext, useEffect } from "react";
|
||||||
|
|
||||||
|
export const DEFAULT_EXPIRY_TIME_MS = 12 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
const AuthContext = createContext({
|
||||||
|
signedIn: false,
|
||||||
|
setSignedIn: () => null,
|
||||||
|
sessionOver: new Date(),
|
||||||
|
setSessionOver: () => null,
|
||||||
|
userId: null,
|
||||||
|
setUserId: () => null,
|
||||||
|
username: "",
|
||||||
|
setUsername: () => null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useAuthContext = () => useContext(AuthContext);
|
||||||
|
|
||||||
|
export const AuthProvider = ({ children }) => {
|
||||||
|
const [signedIn, setSignedIn] = useState(false);
|
||||||
|
const [sessionOver, setSessionOver] = useState(new Date());
|
||||||
|
const [userId, setUserId] = useState(null);
|
||||||
|
const [username, setUsername] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!signedIn) {
|
||||||
|
setUsername(null);
|
||||||
|
setUserId(null);
|
||||||
|
}
|
||||||
|
}, [signedIn]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userId) {
|
||||||
|
localStorage.setItem("userId", userId.toString());
|
||||||
|
}
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (username) {
|
||||||
|
localStorage.setItem("username", username);
|
||||||
|
}
|
||||||
|
}, [username]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let expiry = localStorage.getItem("expiry");
|
||||||
|
if (expiry) {
|
||||||
|
expiry = new Date(expiry);
|
||||||
|
if (Date.now() < expiry.getTime()) {
|
||||||
|
setSignedIn(true);
|
||||||
|
setSessionOver(expiry);
|
||||||
|
// We don't have access to the JWT token as it is an HTTP only cookie -
|
||||||
|
// so we store user info in local storage
|
||||||
|
((username) => {
|
||||||
|
if (username) {
|
||||||
|
setUsername(username);
|
||||||
|
}
|
||||||
|
})(localStorage.getItem("username"));
|
||||||
|
|
||||||
|
((id) => {
|
||||||
|
if (id) {
|
||||||
|
setUserId(parseInt(id, 10));
|
||||||
|
}
|
||||||
|
})(localStorage.getItem("userId"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("expiry", sessionOver.toISOString());
|
||||||
|
setTimeout(() => {
|
||||||
|
setSessionOver((sessionOver) => {
|
||||||
|
if (Date.now() >= sessionOver.getTime()) {
|
||||||
|
setSignedIn((signedIn) => {
|
||||||
|
if (signedIn) {
|
||||||
|
alert(
|
||||||
|
"Session expired. Any further privileged requests will fail until signed in again."
|
||||||
|
);
|
||||||
|
["userId", "userName"].map((x) => localStorage.removeItem(x));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return signedIn;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sessionOver;
|
||||||
|
});
|
||||||
|
}, sessionOver.getTime() - Date.now());
|
||||||
|
}, [sessionOver]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider
|
||||||
|
value={{
|
||||||
|
signedIn,
|
||||||
|
setSignedIn,
|
||||||
|
sessionOver,
|
||||||
|
setSessionOver,
|
||||||
|
userId,
|
||||||
|
setUserId,
|
||||||
|
username,
|
||||||
|
setUsername,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -2,9 +2,11 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
|
|
||||||
|
import { AuthProvider } from "./context/auth_context";
|
||||||
import { Root } from "./root";
|
import { Root } from "./root";
|
||||||
import { Demo } from "./routes/demo";
|
import { Demo } from "./routes/demo";
|
||||||
import { Home } from "./routes/home";
|
import { Home } from "./routes/home";
|
||||||
|
import { AuthSuccessful } from "./routes/auth_successful";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
@ -23,6 +25,10 @@ const router = createBrowserRouter([
|
|||||||
path: "user",
|
path: "user",
|
||||||
element: <Home />,
|
element: <Home />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "auth-successful",
|
||||||
|
element: <AuthSuccessful />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "keys",
|
path: "keys",
|
||||||
element: <Home />,
|
element: <Home />,
|
||||||
@ -32,4 +38,8 @@ const router = createBrowserRouter([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||||
root.render(<RouterProvider router={router} />);
|
root.render(
|
||||||
|
<AuthProvider>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</AuthProvider>
|
||||||
|
);
|
||||||
|
@ -2,22 +2,51 @@ import { Link, Outlet } from "react-router-dom";
|
|||||||
|
|
||||||
import logo from "./assets/chessh_sm.svg";
|
import logo from "./assets/chessh_sm.svg";
|
||||||
|
|
||||||
export const Root = () => (
|
import { useAuthContext, DEFAULT_EXPIRY_TIME_MS } from "./context/auth_context";
|
||||||
|
|
||||||
|
export const Root = () => {
|
||||||
|
const { signedIn, setSignedIn, setSessionOver } = useAuthContext();
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="navbar">
|
<div className="navbar">
|
||||||
<div>
|
<div>
|
||||||
<Link to="/home">
|
<Link to="/home">
|
||||||
<img src={logo} className="logo" />
|
<img src={logo} className="logo" alt="CheSSH Logo" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="nav">
|
<div className="nav">
|
||||||
|
{signedIn ? (
|
||||||
|
<>
|
||||||
<Link className="link" to="/user">
|
<Link className="link" to="/user">
|
||||||
User
|
User
|
||||||
</Link>
|
</Link>
|
||||||
<Link className="link" to="/keys">
|
<Link className="link" to="/keys">
|
||||||
Keys
|
Keys
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
className="link"
|
||||||
|
onClick={() => setSignedIn(false)}
|
||||||
|
to="/"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
onClick={() =>
|
||||||
|
setSessionOver(
|
||||||
|
new Date(Date.now() + DEFAULT_EXPIRY_TIME_MS)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
href={process.env.REACT_APP_GITHUB_OAUTH}
|
||||||
|
className="link"
|
||||||
|
>
|
||||||
|
Login w/ GitHub
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
@ -25,4 +54,5 @@ export const Root = () => (
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
38
front/src/routes/auth_successful.jsx
Normal file
38
front/src/routes/auth_successful.jsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useEffect, useCallback } from "react";
|
||||||
|
import { useAuthContext } from "../context/auth_context";
|
||||||
|
|
||||||
|
export const AuthSuccessful = () => {
|
||||||
|
const {
|
||||||
|
username,
|
||||||
|
signedIn,
|
||||||
|
setSignedIn,
|
||||||
|
setSessionOver,
|
||||||
|
setUserId,
|
||||||
|
setUsername,
|
||||||
|
} = useAuthContext();
|
||||||
|
|
||||||
|
const fetchMyself = useCallback(
|
||||||
|
() =>
|
||||||
|
fetch("/api/player/me", {
|
||||||
|
credentials: "same-origin",
|
||||||
|
})
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((player) => {
|
||||||
|
setSignedIn(!!player);
|
||||||
|
setUserId(player.id);
|
||||||
|
setUsername(player.username);
|
||||||
|
}),
|
||||||
|
[setSessionOver, setSignedIn, setUserId, setUsername]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchMyself();
|
||||||
|
}, [fetchMyself]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Successful Auth</h1>
|
||||||
|
{signedIn ? <p>Hello there, {username || ""}</p> : <p>Loading...</p>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -26,14 +26,14 @@ export const Demo = () => {
|
|||||||
);
|
);
|
||||||
setRenderedPlayer(true);
|
setRenderedPlayer(true);
|
||||||
}
|
}
|
||||||
}, [player]);
|
}, [renderedPlayer, player]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="demo-container">
|
<div className="demo-container">
|
||||||
<h1>
|
<h1>
|
||||||
Welcome to <span style={{ color: "green" }}>> CheSSH!</span>
|
Welcome to <span style={{ color: "green" }}>> CheSSH!</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex-row-around">
|
<div className="flex-row-around">
|
||||||
<p>
|
<p>
|
||||||
CheSSH is a multiplayer, scalable, free, open source, and potentially
|
CheSSH is a multiplayer, scalable, free, open source, and potentially
|
||||||
passwordless game of Chess over the SSH protocol.
|
passwordless game of Chess over the SSH protocol.
|
||||||
@ -42,6 +42,7 @@ export const Demo = () => {
|
|||||||
className="button gold"
|
className="button gold"
|
||||||
href="https://github.com/Simponic/chessh"
|
href="https://github.com/Simponic/chessh"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
🌟 Star 🌟
|
🌟 Star 🌟
|
||||||
</a>
|
</a>
|
||||||
@ -49,10 +50,10 @@ export const Demo = () => {
|
|||||||
<hr />
|
<hr />
|
||||||
<div ref={player} id={demoCastElementId} />
|
<div ref={player} id={demoCastElementId} />
|
||||||
<hr />
|
<hr />
|
||||||
<div class="flex-row-around">
|
<div className="flex-row-around">
|
||||||
<h3>Would you like to play a game?</h3>
|
<h3>Would you like to play a game?</h3>
|
||||||
<Link className="button" to="/home">
|
<Link className="button" to="/home">
|
||||||
Yes, Joshua ↝
|
Yes, Joshua ⇒
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
import { useAuthContext } from "../context/auth_context";
|
||||||
|
|
||||||
export const Home = () => {
|
export const Home = () => {
|
||||||
|
const { username } = useAuthContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Welcome home!</h1>
|
<h1>Welcome home, {username || "guest"}!</h1>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
const { createProxyMiddleware } = require("http-proxy-middleware");
|
||||||
|
|
||||||
module.exports = function(app) {
|
module.exports = function (app) {
|
||||||
if (process.env.NODE_ENV != 'production') {
|
if (process.env.NODE_ENV != "production") {
|
||||||
app.use(
|
app.use(
|
||||||
'/api',
|
"/api",
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: 'http://localhost:8080',
|
target: "http://localhost:8080",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
pathRewrite: (path, _req) => {
|
||||||
|
return path.replace("/api", "");
|
||||||
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,15 @@ defmodule Chessh.Player do
|
|||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defimpl Jason.Encoder, for: Chessh.Player do
|
||||||
|
def encode(value, opts) do
|
||||||
|
Jason.Encode.map(
|
||||||
|
Map.take(value, [:id, :github_id, :username, :created_at, :updated_at]),
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def authentications_changeset(player, attrs) do
|
def authentications_changeset(player, attrs) do
|
||||||
player
|
player
|
||||||
|> cast(attrs, [:authentications])
|
|> cast(attrs, [:authentications])
|
||||||
|
@ -47,12 +47,12 @@ defmodule Chessh.SSH.Daemon do
|
|||||||
:disconnect
|
:disconnect
|
||||||
end
|
end
|
||||||
|
|
||||||
x ->
|
authed_or_disconnect ->
|
||||||
PlayerSession.update_sessions_and_player_satisfies(username, fn _player ->
|
PlayerSession.update_sessions_and_player_satisfies(username, fn _player ->
|
||||||
x
|
authed_or_disconnect
|
||||||
end)
|
end)
|
||||||
|
|
||||||
x
|
authed_or_disconnect
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ defmodule Chessh.SSH.Daemon do
|
|||||||
def handle_info(_, state), do: {:noreply, state}
|
def handle_info(_, state), do: {:noreply, state}
|
||||||
|
|
||||||
defp on_disconnect(_reason) do
|
defp on_disconnect(_reason) do
|
||||||
Logger.debug("#{inspect(self())} disconnected")
|
Logger.info("#{inspect(self())} disconnected")
|
||||||
|
|
||||||
Repo.delete_all(
|
Repo.delete_all(
|
||||||
from(p in PlayerSession,
|
from(p in PlayerSession,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
defmodule Chessh.Web.Token do
|
defmodule Chessh.Web.Token do
|
||||||
use Joken.Config
|
use Joken.Config
|
||||||
|
|
||||||
|
def token_config, do: default_claims(default_exp: 12 * 60 * 60)
|
||||||
end
|
end
|
||||||
|
@ -36,59 +36,27 @@ defmodule Chessh.Web.Endpoint do
|
|||||||
end
|
end
|
||||||
|
|
||||||
{status, body} =
|
{status, body} =
|
||||||
case resp do
|
create_player_from_github_response(resp, github_user_api_url, github_user_agent)
|
||||||
%{"access_token" => access_token} ->
|
|
||||||
case :httpc.request(
|
|
||||||
:get,
|
|
||||||
{String.to_charlist(github_user_api_url),
|
|
||||||
[
|
|
||||||
{'Authorization', String.to_charlist("Bearer #{access_token}")},
|
|
||||||
{'User-Agent', github_user_agent}
|
|
||||||
]},
|
|
||||||
[],
|
|
||||||
[]
|
|
||||||
) do
|
|
||||||
{:ok, {{_, 200, 'OK'}, _, user_details}} ->
|
|
||||||
%{"login" => username, "id" => github_id} =
|
|
||||||
Jason.decode!(String.Chars.to_string(user_details))
|
|
||||||
|
|
||||||
%Player{id: id} =
|
case body do
|
||||||
Repo.insert!(%Player{github_id: github_id, username: username},
|
%{jwt: token} ->
|
||||||
on_conflict: [set: [github_id: github_id]],
|
client_redirect_location =
|
||||||
conflict_target: :github_id
|
Application.get_env(:chessh, Web)[:client_redirect_after_successful_sign_in]
|
||||||
)
|
|
||||||
|
|
||||||
{200,
|
conn
|
||||||
%{
|
|> put_resp_cookie("jwt", token)
|
||||||
success: true,
|
|> put_resp_header("location", client_redirect_location)
|
||||||
jwt:
|
|> send_resp(301, '')
|
||||||
Token.generate_and_sign!(%{
|
|
||||||
"uid" => id
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{400, %{errors: "Access token was incorrect. Try again."}}
|
|
||||||
end
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{400, %{errors: "Failed to retrieve token from GitHub. Try again."}}
|
|
||||||
end
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_resp_content_type("application/json")
|
||||||
|> send_resp(status, Jason.encode!(body))
|
|> send_resp(status, Jason.encode!(body))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
put "/player/password" do
|
put "/player/password" do
|
||||||
jwt =
|
player = get_player_from_jwt(conn)
|
||||||
Enum.find_value(conn.req_headers, fn {header, value} ->
|
|
||||||
if header === "authorization", do: value
|
|
||||||
end)
|
|
||||||
|
|
||||||
{:ok, %{"uid" => uid}} = Token.verify_and_validate(jwt)
|
|
||||||
|
|
||||||
player = Repo.get(Player, uid)
|
|
||||||
|
|
||||||
{status, body} =
|
{status, body} =
|
||||||
case conn.body_params do
|
case conn.body_params do
|
||||||
@ -151,17 +119,13 @@ defmodule Chessh.Web.Endpoint do
|
|||||||
end
|
end
|
||||||
|
|
||||||
post "/player/keys" do
|
post "/player/keys" do
|
||||||
jwt =
|
player = get_player_from_jwt(conn)
|
||||||
Enum.find_value(conn.req_headers, fn {header, value} ->
|
|
||||||
if header === "authorization", do: value
|
|
||||||
end)
|
|
||||||
|
|
||||||
{:ok, %{"uid" => uid}} = Token.verify_and_validate(jwt)
|
|
||||||
|
|
||||||
{status, body} =
|
{status, body} =
|
||||||
case conn.body_params do
|
case conn.body_params do
|
||||||
%{"key" => key, "name" => name} ->
|
%{"key" => key, "name" => name} ->
|
||||||
case Key.changeset(%Key{}, %{player_id: uid, key: key, name: name}) |> Repo.insert() do
|
case Key.changeset(%Key{}, %{player_id: player.id, key: key, name: name})
|
||||||
|
|> Repo.insert() do
|
||||||
{:ok, _new_key} ->
|
{:ok, _new_key} ->
|
||||||
{
|
{
|
||||||
200,
|
200,
|
||||||
@ -191,6 +155,14 @@ defmodule Chessh.Web.Endpoint do
|
|||||||
|> send_resp(status, Jason.encode!(body))
|
|> send_resp(status, Jason.encode!(body))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get "/player/me" do
|
||||||
|
player = get_player_from_jwt(conn)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(200, Jason.encode!(player))
|
||||||
|
end
|
||||||
|
|
||||||
get "/player/:id/keys" do
|
get "/player/:id/keys" do
|
||||||
%{"id" => player_id} = conn.path_params
|
%{"id" => player_id} = conn.path_params
|
||||||
|
|
||||||
@ -198,22 +170,17 @@ defmodule Chessh.Web.Endpoint do
|
|||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_resp_content_type("application/json")
|
||||||
|> send_resp(200, Jason.encode!(%{keys: keys}))
|
|> send_resp(200, Jason.encode!(keys))
|
||||||
end
|
end
|
||||||
|
|
||||||
delete "/keys/:id" do
|
delete "/keys/:id" do
|
||||||
jwt =
|
player = get_player_from_jwt(conn)
|
||||||
Enum.find_value(conn.req_headers, fn {header, value} ->
|
|
||||||
if header === "authorization", do: value
|
|
||||||
end)
|
|
||||||
|
|
||||||
{:ok, %{"uid" => uid}} = Token.verify_and_validate(jwt)
|
|
||||||
|
|
||||||
%{"id" => key_id} = conn.path_params
|
%{"id" => key_id} = conn.path_params
|
||||||
key = Repo.get(Key, key_id)
|
key = Repo.get(Key, key_id)
|
||||||
|
|
||||||
{status, body} =
|
{status, body} =
|
||||||
if key && uid == key.player_id do
|
if key && player.id == key.player_id do
|
||||||
case Repo.delete(key) do
|
case Repo.delete(key) do
|
||||||
{:ok, _} ->
|
{:ok, _} ->
|
||||||
{200, %{success: true}}
|
{200, %{success: true}}
|
||||||
@ -258,4 +225,58 @@ defmodule Chessh.Web.Endpoint do
|
|||||||
fn key -> Application.get_env(:chessh, Web)[key] end
|
fn key -> Application.get_env(:chessh, Web)[key] end
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp get_player_from_jwt(conn) do
|
||||||
|
auth_header =
|
||||||
|
Enum.find_value(conn.req_headers, fn {header, value} ->
|
||||||
|
if header === "authorization", do: value
|
||||||
|
end)
|
||||||
|
|
||||||
|
jwt = if auth_header, do: auth_header, else: Map.get(fetch_cookies(conn).cookies, "jwt")
|
||||||
|
|
||||||
|
{:ok, %{"uid" => uid}} = Token.verify_and_validate(jwt)
|
||||||
|
|
||||||
|
Repo.get(Player, uid)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_player_from_github_response(resp, github_user_api_url, github_user_agent) do
|
||||||
|
case resp do
|
||||||
|
%{"access_token" => access_token} ->
|
||||||
|
case :httpc.request(
|
||||||
|
:get,
|
||||||
|
{String.to_charlist(github_user_api_url),
|
||||||
|
[
|
||||||
|
{'Authorization', String.to_charlist("Bearer #{access_token}")},
|
||||||
|
{'User-Agent', github_user_agent}
|
||||||
|
]},
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
) do
|
||||||
|
{:ok, {{_, 200, 'OK'}, _, user_details}} ->
|
||||||
|
%{"login" => username, "id" => github_id} =
|
||||||
|
Jason.decode!(String.Chars.to_string(user_details))
|
||||||
|
|
||||||
|
%Player{id: id} =
|
||||||
|
Repo.insert!(%Player{github_id: github_id, username: username},
|
||||||
|
on_conflict: [set: [github_id: github_id]],
|
||||||
|
conflict_target: :github_id
|
||||||
|
)
|
||||||
|
|
||||||
|
{200,
|
||||||
|
%{
|
||||||
|
success: true,
|
||||||
|
jwt:
|
||||||
|
Token.generate_and_sign!(%{
|
||||||
|
"uid" => id
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{400, %{errors: "Access token was incorrect. Try again."}}
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{400, %{errors: "Failed to retrieve token from GitHub. Try again."}}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "chessh",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user