From 5556b04a41d931faeed017a6a5c88274da1c81c1 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Mon, 26 Oct 2020 23:17:24 -0400 Subject: [PATCH 01/12] vote result page --- client/src/components/App.tsx | 7 +- client/src/components/Pages/Result/index.tsx | 146 ++++++++++++++++++ client/src/components/Pages/Result/result.css | 32 ++++ client/src/components/Pages/Vote/index.tsx | 4 +- client/src/components/index.ts | 1 + service/index.ts | 13 +- 6 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 client/src/components/Pages/Result/index.tsx create mode 100644 client/src/components/Pages/Result/result.css diff --git a/client/src/components/App.tsx b/client/src/components/App.tsx index a25abec..8e4e716 100644 --- a/client/src/components/App.tsx +++ b/client/src/components/App.tsx @@ -1,7 +1,7 @@ import React, { Component } from "react"; import { Switch, Route, BrowserRouter as Router } from "react-router-dom"; import { withOidcSecure } from "@axa-fr/react-oidc-context"; -import { Home, Vote, Create } from "./index"; +import { Home, Vote, Create, Result } from "./index"; import PageContainer from "../containers/PageContainer"; class App extends Component { @@ -17,6 +17,11 @@ class App extends Component { component={withOidcSecure(Vote)} /> + diff --git a/client/src/components/Pages/Result/index.tsx b/client/src/components/Pages/Result/index.tsx new file mode 100644 index 0000000..2c541f6 --- /dev/null +++ b/client/src/components/Pages/Result/index.tsx @@ -0,0 +1,146 @@ +import React, { useEffect, useState } from "react"; +import { useParams, useHistory } from 'react-router-dom'; +import Spinner from "../../Spinner"; +import "./result.css"; + +type RouteParams = { + voteId: string +} + +type Poll = { + name: string, + results: Object +} + +export const Result: React.FunctionComponent = () =>{ + const { voteId } = useParams(); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + + const [poll, setPoll] = useState(undefined); + const [ended, setEnded] = useState(false); + const evals = true; + let history = useHistory(); + + useEffect(() => { + const interval = setInterval(() => { + if(!ended) { + fetch("http://localhost:5000/api/getCount", { + headers: {"content-type": "application/json"}, + method: "POST", + body: JSON.stringify({"voteId": voteId}) + }) + .then((res) => { + switch(res.status) { + case 200: + return res.json() + case 404: + return undefined + default: + throw new Error("Error Getting Poll Details"); + } + }) + .then((result) => { + setLoading(false); + setPoll(result) + console.log(result) + }, + (error) => { + setLoading(false); + setError(true); + console.log(error); + }); + } + }, 10000); + return () => clearInterval(interval); + }, [voteId, ended]); + + useEffect(() => { + fetch("http://localhost:5000/api/getCount", { + headers: {"content-type": "application/json"}, + method: "POST", + body: JSON.stringify({"voteId": voteId}) + }) + .then((res) => { + switch(res.status) { + case 200: + return res.json() + case 404: + return undefined + default: + throw new Error("Error Getting Poll Details"); + } + }) + .then((result) => { + setLoading(false); + setPoll(result) + console.log(result) + }, + (error) => { + setLoading(false); + setError(true); + console.log(error); + }); + }, [voteId]) + + function endVoting() { + if(window.confirm("End Voting?")){ + setLoading(true) + fetch("http://localhost:5000/api/endPoll", { + headers: {"content-type": "application/json"}, + method: "POST", + body: JSON.stringify({"voteId": voteId}) + }) + .then((res) => { + switch(res.status) { + case 200: + return res.json() + case 404: + return undefined + default: + throw new Error("Error Getting Poll Details"); + } + }) + .then((result) => { + setLoading(false); + setPoll(result) + setEnded(true) + console.log(result) + }, + (error) => { + setLoading(false); + setError(true); + console.log(error); + }); + } + } + + return( + loading ? : + error ?
Something went wrong : (
: + poll === undefined ? +
Poll not found : (
: +
+
+
{poll.name} {ended ? "Final" : null} Results
+
+ {ended? null :
These are refreshed every 10 seconds
} +
+ {Object.entries(poll.results).map(function(option, idx){ + return (
  • {option[0]} : {option[1]}
  • ) + })} +
    +
    +
    +
    + {evals && !ended ? : } +
    + +
    + ) +} + +export default Result; diff --git a/client/src/components/Pages/Result/result.css b/client/src/components/Pages/Result/result.css new file mode 100644 index 0000000..cf779e1 --- /dev/null +++ b/client/src/components/Pages/Result/result.css @@ -0,0 +1,32 @@ +.result-title-panel { + color: inherit; + font-size: 20px; + padding: 20px; +} + +.result-list { + border: none; + border-radius: 2px; + box-shadow: 0 1px 4px rgba(0,0,0,.3); +} + +.refresh-message { + padding: 12px; +} + +.result-list-items{ + background-color: white; + font-size: 14px; + padding:12px; + padding-right: 16px; +} + +.result-list-items li { + list-style: none; + padding-top: 12px; +} + +.exit-button { + margin-top: 24px; + text-align: center; +} diff --git a/client/src/components/Pages/Vote/index.tsx b/client/src/components/Pages/Vote/index.tsx index d45a197..32ea94a 100644 --- a/client/src/components/Pages/Vote/index.tsx +++ b/client/src/components/Pages/Vote/index.tsx @@ -58,9 +58,7 @@ export const Vote: React.FunctionComponent = () =>{ .then((res) => { switch(res.status) { case 204: - //TODO- probably better to route to a "voted" screen than home - //but this is prob okay for mvp - history.push("/") + history.push("/result/" + voteId) return; default: throw new Error("Error Voting"); diff --git a/client/src/components/index.ts b/client/src/components/index.ts index 2613c3d..a57b7c1 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -2,3 +2,4 @@ export { default as Home } from "./Pages/Home"; export { default as NavBar } from "./NavBar"; export { default as Vote } from "./Pages/Vote"; export { default as Create } from "./Pages/Create"; +export { default as Result } from "./Pages/Result"; diff --git a/service/index.ts b/service/index.ts index 36c140e..e7d8f87 100644 --- a/service/index.ts +++ b/service/index.ts @@ -11,6 +11,11 @@ let currentPolls = [{"id": 1, "name": "eat a whole cake conditional", "voteOptio {"id": 2, "name": "fail chad", "voteOptions": ["fail", "conditional", "abstain"]} ]; +const sampleCount = { + "name": "Potate's Vote", + "results": {"option1": 1, "option2": 2, "option3": 0} +} + app.use(express.static(path.join(__dirname, "/../build"))); // Returns list of all current polls @@ -46,13 +51,13 @@ app.post("/api/initializePoll", (req,res) => { }); // get the count without ending the poll -app.get("/api/getCount", (req,res) => { - res.json({"countYes": 1, "countNo": 2, "countAbstain": 0, "totalCount": 3}); +app.post("/api/getCount", (req,res) => { + res.json(sampleCount); }); // end the poll and get the final results, called from evals view -app.get("/api/endPoll", (req,res) => { - res.json({"countYes": 1, "countNo": 2, "countAbstain": 0, "totalCount": 3}); +app.post("/api/endPoll", (req,res) => { + res.json(sampleCount); }); /** * TODO - what other endpoints will we need? From 704b65231198863d940fa35a161acdd5030a5a46 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Thu, 29 Oct 2020 01:06:44 -0400 Subject: [PATCH 02/12] attempt to make oidc work --- client/.env | 2 +- client/src/components/Pages/Home/index.tsx | 1 + package.json | 5 ++ service/index.ts | 75 +++++++++++++++++++++- 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/client/.env b/client/.env index 02f2863..fea247c 100644 --- a/client/.env +++ b/client/.env @@ -1,2 +1,2 @@ -REACT_APP_SSO_CLIENT_ID=react-boilerplate +REACT_APP_SSO_CLIENT_ID=vote REACT_APP_SSO_AUTHORITY=https://sso.csh.rit.edu/auth/realms/csh \ No newline at end of file diff --git a/client/src/components/Pages/Home/index.tsx b/client/src/components/Pages/Home/index.tsx index e0032ed..52ba565 100644 --- a/client/src/components/Pages/Home/index.tsx +++ b/client/src/components/Pages/Home/index.tsx @@ -27,6 +27,7 @@ class Home extends Component{ }); }, (error) => { + console.log(error) this.setState({ isLoaded: true, error diff --git a/package.json b/package.json index 4b73b0e..69ad2f9 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,17 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", + "@types/express-session": "^1.17.0", "body-parser": "^1.19.0", + "connect-ensure-login": "^0.1.1", "cors": "^2.8.5", "csh-material-bootstrap": "^4.5.2", "express": "^4.17.1", + "express-session": "^1.17.1", "history": "^5.0.0", "oidc-client": "^1.10.1", + "passport": "^0.4.1", + "passport-openidconnect": "0.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "^5.2.0", diff --git a/service/index.ts b/service/index.ts index 36c140e..350d956 100644 --- a/service/index.ts +++ b/service/index.ts @@ -1,17 +1,88 @@ import * as express from "express"; import * as path from "path"; import * as cors from "cors"; +import * as dotenv from 'dotenv'; + +dotenv.config() + +// Configure the OpenID Connect strategy for use by Passport. +const passport = require('passport'); +const Strategy = require('passport-openidconnect').Strategy; +passport.use( + new Strategy( + { + issuer: 'https://sso.csh.rit.edu/auth/realms/csh', + authorizationURL: + 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/auth', + tokenURL: + 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/token', + userInfoURL: + 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/userinfo', + clientID: process.env.CLIENT_ID, + clientSecret: process.env.CLIENT_SECRET, + callbackURL: 'http://' + process.env.HOST + '/login/callback', + }, + (accessToken, refreshToken, profile, cb) => { + return cb(null, profile); + } + ) +); + +// Configure Passport authenticated session persistence. +passport.serializeUser((user, cb) => { + cb(null, user); +}); + +passport.deserializeUser((obj, cb) => { + cb(null, obj); +}); const app = express(); -app.use(cors()); + +app.use( + require('express-session')({ + secret:" process.env.EXPRESS_SESSION_SECRET", + resave: true, + saveUninitialized: true, + }) + ); +// Initialize Passport and restore authentication state from the session. +app.use(passport.initialize()); + +app.use(passport.session()); + + app.use(express.json()); +app.use(express.static(path.join(__dirname, "/../build"))); +app.use(cors()); + +// Authentication: authenticates with CSH OIDC and returns to origin point +app.get('/login', passport.authenticate('openidconnect')); + +app.get( + '/login/callback', + passport.authenticate('openidconnect', { + failureRedirect: '/login', + }), + (req, res) => { + res.redirect(req.session.returnTo); + } + ); + +app.get('/logout', (req, res) => { + // At the moment, this doesn't actually log you out + // Apparently I can't destroy sso sessions? + req.session.destroy((err) => { + res.redirect('/'); + }); + }); let currentPolls = [{"id": 1, "name": "eat a whole cake conditional", "voteOptions": ["eat 1 cake","eat 2 cakes", "eat no cakes : ("]}, {"id": 2, "name": "fail chad", "voteOptions": ["fail", "conditional", "abstain"]} ]; -app.use(express.static(path.join(__dirname, "/../build"))); +app.use(require('connect-ensure-login').ensureLoggedIn()); // Returns list of all current polls app.get("/api/getCurrentPolls", (req,res) => { From 7b454bd4fbb6de17bfb5983763762800f4b50d97 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Thu, 29 Oct 2020 21:50:02 -0400 Subject: [PATCH 03/12] start doing db things --- client/src/components/NavBar/Profile.tsx | 1 + client/src/components/Pages/Vote/index.tsx | 12 ++-- client/src/components/PollList/index.tsx | 6 +- package.json | 1 + service/database.ts | 39 ++++++++++++ service/index.ts | 73 +++++++++++++++++----- 6 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 service/database.ts diff --git a/client/src/components/NavBar/Profile.tsx b/client/src/components/NavBar/Profile.tsx index ff18536..4bd0ef7 100644 --- a/client/src/components/NavBar/Profile.tsx +++ b/client/src/components/NavBar/Profile.tsx @@ -11,6 +11,7 @@ const Profile: React.FunctionComponent = () => { const { oidcUser, logout } = useReactOidc(); if (!oidcUser) return null; + console.log(oidcUser); const { profile: { name, preferred_username }, } = oidcUser; diff --git a/client/src/components/Pages/Vote/index.tsx b/client/src/components/Pages/Vote/index.tsx index 32ea94a..c5c66e8 100644 --- a/client/src/components/Pages/Vote/index.tsx +++ b/client/src/components/Pages/Vote/index.tsx @@ -6,9 +6,9 @@ type RouteParams = { voteId: string } type Poll = { - id: string, - name: string, - voteOptions: Array, + _id: string, + title: string, + choices: Array, } export const Vote: React.FunctionComponent = () =>{ @@ -78,16 +78,16 @@ export const Vote: React.FunctionComponent = () =>{
    Poll not found : (
    :
    -
    {poll.name}
    +
    {poll.title}
    - {poll.voteOptions.map(function(option, idx){ + {poll.choices.map(function(option, idx){ return (
  • ) })}
    -
    You have selected: { selected !== null ? poll.voteOptions[selected] : null}
    +
    You have selected: { selected !== null ? poll.choices[selected] : null}

    diff --git a/package.json b/package.json index 4b73b0e..9b920bb 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "csh-material-bootstrap": "^4.5.2", "express": "^4.17.1", "history": "^5.0.0", + "mongodb": "^3.6.2", "oidc-client": "^1.10.1", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/service/database.ts b/service/database.ts new file mode 100644 index 0000000..5414b72 --- /dev/null +++ b/service/database.ts @@ -0,0 +1,39 @@ +var ObjectID = require('mongodb').ObjectID; + +export function insertPoll(db, poll) { + Promise.resolve(db).then((res) =>{ + const collection = res.collection("Polls") + collection.insert(poll, function(err,docsInserted){ + console.log(poll._id); + return(poll._id) + }); + }) + } + + +export async function insertVote(db) { + const vote = { + time: new Date(), + userName: "rebeccas", + choice: "abstain", + poll: ObjectID.createFromHexString("5f9b2d5d601e1c6971430638") + } + const findData = { + userName: "rebeccas", + poll: ObjectID.createFromHexString("5f9b2d5d601e1c6971430638") + } + await Promise.resolve(db); + const collection = db.collection("Votes") + collection.findOne(findData).then(res => { + if(res) { + throw new Error("Already Voted"); + } else { + collection.insert(vote, function(err,docsInserted){ + console.log(docsInserted); + }); + } + }).catch(() => { + return false; + }); + return true; +} diff --git a/service/index.ts b/service/index.ts index e7d8f87..24f2da6 100644 --- a/service/index.ts +++ b/service/index.ts @@ -1,14 +1,30 @@ import * as express from "express"; import * as path from "path"; import * as cors from "cors"; +import {insertPoll, insertVote} from "./database"; +var MongoClient = require('mongodb').MongoClient +import * as dotenv from 'dotenv'; +var ObjectID = require('mongodb').ObjectID; + +dotenv.config(); + +let db = null; + +MongoClient.connect(process.env.DB_URL, function (err, client) { + if (err) throw(err) + db = client.db("vote") +}) const app = express(); app.use(cors()); app.use(express.json()); -let currentPolls = [{"id": 1, "name": "eat a whole cake conditional", "voteOptions": ["eat 1 cake","eat 2 cakes", "eat no cakes : ("]}, -{"id": 2, "name": "fail chad", "voteOptions": ["fail", "conditional", "abstain"]} +let currentPolls = [ + { title: 'test Poll', + choices: ["fail", "conditional", "abstain"], + type: 'FailConditional', + _id: "5f9b2d5d601e1c6971430638" } ]; const sampleCount = { @@ -24,7 +40,8 @@ app.get("/api/getCurrentPolls", (req,res) => { }); app.post("/api/getPollDetails", (req,res) => { - const poll = currentPolls.find(x => x.id === parseInt(req.body.voteId)); + console.log(currentPolls) + const poll = currentPolls.find(x => x._id === req.body.voteId); if (poll) { res.json(poll) } else { @@ -34,20 +51,51 @@ app.post("/api/getPollDetails", (req,res) => { // Send in a client's vote, called from any voting screen app.post("/api/sendVote", (req,res) => { - res.status(204).send(); + //TODO - replace this with the actual vote request + //need to get the userName from oidc + const vote = { + time: new Date(), + userName: "rebeccas", + choice: "abstain", + poll: ObjectID.createFromHexString("5f9b2d5d601e1c6971430638") + } + const findData = { + userName: "rebeccas", + poll: ObjectID.createFromHexString("5f9b2d5d601e1c6971430638") + } + + + const collection = db.collection("Votes"); + + collection.findOne(findData).then(hasVoted => { + if(hasVoted) { + res.status(400).send(); + } else { + collection.insert(vote, function(err,docsInserted){ + console.log(docsInserted); + res.status(204).send(); + }); + } + }) }); // Initialize a poll/vote, called from eval's init screen app.post("/api/initializePoll", (req,res) => { - // TODO - the id should actually be generated by putting it in the db - // for now it's just the idx in the list since we're not doing db things yet const newPoll = { - "id": currentPolls.length + 1, - "name": req.body.title, - "voteOptions": req.body.options + "_id": null, + "title": req.body.title, + "choices": req.body.options, + "type": req.body.type, + "time": new Date() } + const collection = db.collection("Polls") + collection.insert(newPoll, function(err,docsInserted){ + console.log(docsInserted); + console.log(newPoll); + newPoll._id = newPoll._id.toHexString(); + }); currentPolls.push(newPoll); - res.json({"pollId": newPoll.id}); + res.json({"pollId": newPoll._id}); }); // get the count without ending the poll @@ -59,10 +107,7 @@ app.post("/api/getCount", (req,res) => { app.post("/api/endPoll", (req,res) => { res.json(sampleCount); }); -/** - * TODO - what other endpoints will we need? - * Will Evals need to get a list of all who voted (to poke people if they're slow)? - */ + app.get('*', (req,res) =>{ res.sendFile(path.join(__dirname+"/../build/index.html")); }); From 918ee2d6652cef6b7de3e47b3daf28e41f9eb091 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Fri, 30 Oct 2020 00:41:27 -0400 Subject: [PATCH 04/12] attempt to use bearer token --- client/src/components/Pages/Home/index.tsx | 59 +++++------ package.json | 1 + service/index.ts | 108 +++++++++------------ 3 files changed, 74 insertions(+), 94 deletions(-) diff --git a/client/src/components/Pages/Home/index.tsx b/client/src/components/Pages/Home/index.tsx index 52ba565..59a4903 100644 --- a/client/src/components/Pages/Home/index.tsx +++ b/client/src/components/Pages/Home/index.tsx @@ -1,6 +1,7 @@ -import React, {Component} from "react"; +import React, {useEffect, useState} from "react"; import Spinner from "../../Spinner"; import PollList from "../../PollList"; +import { useReactOidc } from "@axa-fr/react-oidc-context"; type HomeProps = {}; type HomeState = { @@ -8,45 +9,37 @@ type HomeState = { isLoaded: boolean, currentPolls: Array } -class Home extends Component{ - constructor(props:HomeProps) { - super(props); - this.state = { - error: null, - isLoaded: false, - currentPolls: [] - }; - } - componentDidMount() { - fetch("http://localhost:5000/api/getCurrentPolls") +export const Home: React.FunctionComponent = () =>{ + const [error, setError] = useState(null); + const [isLoaded, setLoaded] = useState(false); + const [currentPolls, setCurrentPolls] = useState([]); + const { oidcUser } = useReactOidc(); + + useEffect(() => { + + fetch("http://localhost:5000/api/getCurrentPolls", { headers: new Headers({ + 'Authorization': 'Bearer ' + oidcUser.access_token + })}) .then(res => res.json()) .then((result) => { - this.setState({ - isLoaded: true, - currentPolls: result - }); + setLoaded(true); + setCurrentPolls(result); }, (error) => { console.log(error) - this.setState({ - isLoaded: true, - error - }); + setLoaded(true); + setError(error); }); - } - render() { - const {error, isLoaded, currentPolls} = this.state; - if (error) { - return( + }, []) + + return ( + error ?
    Something went wrong! Please Try again
    - ); - } else if(!isLoaded) { - return() - }else { - return( - - )} - } + : !isLoaded ? + + : + ) + } export default Home; diff --git a/package.json b/package.json index 69ad2f9..c22aab1 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "history": "^5.0.0", "oidc-client": "^1.10.1", "passport": "^0.4.1", + "passport-jwt": "^4.0.0", "passport-openidconnect": "0.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/service/index.ts b/service/index.ts index 350d956..d521919 100644 --- a/service/index.ts +++ b/service/index.ts @@ -2,90 +2,76 @@ import * as express from "express"; import * as path from "path"; import * as cors from "cors"; import * as dotenv from 'dotenv'; - dotenv.config() -// Configure the OpenID Connect strategy for use by Passport. const passport = require('passport'); -const Strategy = require('passport-openidconnect').Strategy; -passport.use( - new Strategy( - { - issuer: 'https://sso.csh.rit.edu/auth/realms/csh', - authorizationURL: - 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/auth', - tokenURL: - 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/token', - userInfoURL: - 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/userinfo', - clientID: process.env.CLIENT_ID, - clientSecret: process.env.CLIENT_SECRET, - callbackURL: 'http://' + process.env.HOST + '/login/callback', - }, - (accessToken, refreshToken, profile, cb) => { - return cb(null, profile); - } - ) -); + +var JwtStrategy = require('passport-jwt').Strategy, + ExtractJwt = require('passport-jwt').ExtractJwt; +var opts = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.CLIENT_SECRET, + issuer: 'https://sso.csh.rit.edu/auth/realms/csh' +} +passport.use(new JwtStrategy(opts, function(jwt_payload, done) { + console.log(jwt_payload) + return done(); + })); + + +// passport.use( +// new Strategy( +// { +// issuer: , +// authorizationURL: +// 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/auth', +// tokenURL: +// 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/token', +// userInfoURL: +// 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/userinfo', +// clientID: process.env.CLIENT_ID, +// clientSecret: process.env.CLIENT_SECRET }, +// (accessToken, refreshToken, profile, cb) => { +// return cb(null, profile); +// } +// ) +// ); // Configure Passport authenticated session persistence. -passport.serializeUser((user, cb) => { - cb(null, user); -}); +// passport.serializeUser((user, cb) => { +// cb(null, user); +// }); -passport.deserializeUser((obj, cb) => { - cb(null, obj); -}); +// passport.deserializeUser((obj, cb) => { +// cb(null, obj); +// }); const app = express(); -app.use( - require('express-session')({ - secret:" process.env.EXPRESS_SESSION_SECRET", - resave: true, - saveUninitialized: true, - }) - ); -// Initialize Passport and restore authentication state from the session. +// app.use( +// require('express-session')({ +// secret:" process.env.EXPRESS_SESSION_SECRET", +// resave: true, +// saveUninitialized: true, +// }) +// ); +// // Initialize Passport and restore authentication state from the session. app.use(passport.initialize()); -app.use(passport.session()); - +// app.use(passport.session()); app.use(express.json()); app.use(express.static(path.join(__dirname, "/../build"))); app.use(cors()); -// Authentication: authenticates with CSH OIDC and returns to origin point -app.get('/login', passport.authenticate('openidconnect')); - -app.get( - '/login/callback', - passport.authenticate('openidconnect', { - failureRedirect: '/login', - }), - (req, res) => { - res.redirect(req.session.returnTo); - } - ); - -app.get('/logout', (req, res) => { - // At the moment, this doesn't actually log you out - // Apparently I can't destroy sso sessions? - req.session.destroy((err) => { - res.redirect('/'); - }); - }); - let currentPolls = [{"id": 1, "name": "eat a whole cake conditional", "voteOptions": ["eat 1 cake","eat 2 cakes", "eat no cakes : ("]}, {"id": 2, "name": "fail chad", "voteOptions": ["fail", "conditional", "abstain"]} ]; -app.use(require('connect-ensure-login').ensureLoggedIn()); // Returns list of all current polls -app.get("/api/getCurrentPolls", (req,res) => { +app.get("/api/getCurrentPolls", passport.authenticate('jwt', { session: false }), (req,res) => { res.json(currentPolls); }); From a1c6092ed5e01eaee4b5e0797b9c8cb1e654ad07 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Fri, 30 Oct 2020 02:07:11 -0400 Subject: [PATCH 05/12] implement getcount, endpoll --- client/src/components/Pages/Create/index.tsx | 3 +- client/src/components/Pages/Vote/index.tsx | 8 ++-- service/index.ts | 46 +++++++++++++++++--- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/client/src/components/Pages/Create/index.tsx b/client/src/components/Pages/Create/index.tsx index 84098aa..efb5d00 100644 --- a/client/src/components/Pages/Create/index.tsx +++ b/client/src/components/Pages/Create/index.tsx @@ -52,7 +52,8 @@ export const Create: React.FunctionComponent = () =>{ setLoading(true); const body = { "title": pollTitle, - "options": pollOptions + "options": pollOptions, + "type": pollType } fetch("http://localhost:5000/api/initializePoll", { headers: {"content-type": "application/json"}, diff --git a/client/src/components/Pages/Vote/index.tsx b/client/src/components/Pages/Vote/index.tsx index c5c66e8..fe0eafe 100644 --- a/client/src/components/Pages/Vote/index.tsx +++ b/client/src/components/Pages/Vote/index.tsx @@ -65,9 +65,11 @@ export const Vote: React.FunctionComponent = () =>{ } }) .catch((error) => { - setLoading(false); - setError(true); - console.log(error); + history.push("/result/" + voteId) + + // setLoading(false); + // setError(true); + // console.log(error); }); } } diff --git a/service/index.ts b/service/index.ts index 24f2da6..690f49c 100644 --- a/service/index.ts +++ b/service/index.ts @@ -40,7 +40,6 @@ app.get("/api/getCurrentPolls", (req,res) => { }); app.post("/api/getPollDetails", (req,res) => { - console.log(currentPolls) const poll = currentPolls.find(x => x._id === req.body.voteId); if (poll) { res.json(poll) @@ -72,7 +71,6 @@ app.post("/api/sendVote", (req,res) => { res.status(400).send(); } else { collection.insert(vote, function(err,docsInserted){ - console.log(docsInserted); res.status(204).send(); }); } @@ -90,8 +88,6 @@ app.post("/api/initializePoll", (req,res) => { } const collection = db.collection("Polls") collection.insert(newPoll, function(err,docsInserted){ - console.log(docsInserted); - console.log(newPoll); newPoll._id = newPoll._id.toHexString(); }); currentPolls.push(newPoll); @@ -100,12 +96,50 @@ app.post("/api/initializePoll", (req,res) => { // get the count without ending the poll app.post("/api/getCount", (req,res) => { - res.json(sampleCount); + const count = {name: "", results:{}} + const pollCollection = db.collection("Polls") + pollCollection.findOne({"_id": ObjectID.createFromHexString(req.body.voteId)}).then(poll => { + count.name = poll.title; + }); + const collection = db.collection("Votes") + collection.find({"poll": ObjectID.createFromHexString(req.body.voteId)}).toArray(function(err, result) { + if (err) throw err; + result.forEach(element => { + const choice = element.choice; + if (count.results[choice]) { + count.results[choice] += 1; + } else { + count.results[choice] = 1; + } + }); + res.json(count); + }); }); // end the poll and get the final results, called from evals view app.post("/api/endPoll", (req,res) => { - res.json(sampleCount); + var removeIndex = currentPolls.map(item => item._id) + .indexOf(req.body.voteId); + ~removeIndex && currentPolls.splice(removeIndex, 1); + + const count = {name: "", results:{}} + const pollCollection = db.collection("Polls") + pollCollection.findOne({"_id": ObjectID.createFromHexString(req.body.voteId)}).then(poll => { + count.name = poll.title; + }); + const collection = db.collection("Votes") + collection.find({"poll": ObjectID.createFromHexString(req.body.voteId)}).toArray(function(err, result) { + if (err) throw err; + result.forEach(element => { + const choice = element.choice; + if (count.results[choice]) { + count.results[choice] += 1; + } else { + count.results[choice] = 1; + } + }); + res.json(count); + }); }); app.get('*', (req,res) =>{ From 0bc48431d6621d2edf9f04045ee9739be0ed38df Mon Sep 17 00:00:00 2001 From: Rebecca Date: Fri, 30 Oct 2020 11:04:10 -0400 Subject: [PATCH 06/12] backend auth works --- client/src/components/Pages/Create/index.tsx | 8 +- client/src/components/Pages/Home/index.tsx | 6 -- client/src/components/Pages/Vote/index.tsx | 13 ++- package.json | 4 +- service/index.ts | 95 ++++++++++---------- 5 files changed, 67 insertions(+), 59 deletions(-) diff --git a/client/src/components/Pages/Create/index.tsx b/client/src/components/Pages/Create/index.tsx index 84098aa..aaaea5c 100644 --- a/client/src/components/Pages/Create/index.tsx +++ b/client/src/components/Pages/Create/index.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { useHistory } from 'react-router-dom'; import Spinner from "../../Spinner"; import "./create.css"; +import { useReactOidc } from "@axa-fr/react-oidc-context"; export const Create: React.FunctionComponent = () =>{ const [pollType, setPollType] = useState("PassFail"); @@ -9,6 +10,8 @@ export const Create: React.FunctionComponent = () =>{ const [pollOptions, setPollOptions] = useState(["Pass", "Fail or Conditional", "Abstain"]); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); + const { oidcUser } = useReactOidc(); + let history = useHistory(); function handleSelectPollType(changeEvent:React.FormEvent) { @@ -55,7 +58,10 @@ export const Create: React.FunctionComponent = () =>{ "options": pollOptions } fetch("http://localhost:5000/api/initializePoll", { - headers: {"content-type": "application/json"}, + headers: new Headers({ + 'Authorization': 'Bearer ' + oidcUser.access_token, + "content-type": "application/json" + }), method: "POST", body: JSON.stringify(body) }) diff --git a/client/src/components/Pages/Home/index.tsx b/client/src/components/Pages/Home/index.tsx index 59a4903..9588c74 100644 --- a/client/src/components/Pages/Home/index.tsx +++ b/client/src/components/Pages/Home/index.tsx @@ -3,12 +3,6 @@ import Spinner from "../../Spinner"; import PollList from "../../PollList"; import { useReactOidc } from "@axa-fr/react-oidc-context"; -type HomeProps = {}; -type HomeState = { - error: null | string, - isLoaded: boolean, - currentPolls: Array -} export const Home: React.FunctionComponent = () =>{ const [error, setError] = useState(null); const [isLoaded, setLoaded] = useState(false); diff --git a/client/src/components/Pages/Vote/index.tsx b/client/src/components/Pages/Vote/index.tsx index d45a197..d1e2b6b 100644 --- a/client/src/components/Pages/Vote/index.tsx +++ b/client/src/components/Pages/Vote/index.tsx @@ -2,6 +2,8 @@ import React, { useEffect, useState } from "react"; import { useParams, useHistory } from 'react-router-dom'; import Spinner from "../../Spinner"; import "./vote.css"; +import { useReactOidc } from "@axa-fr/react-oidc-context"; + type RouteParams = { voteId: string } @@ -18,12 +20,16 @@ export const Vote: React.FunctionComponent = () =>{ const [error, setError] = useState(false); const [selected, setSelected] = useState(null); + const { oidcUser } = useReactOidc(); let history = useHistory(); useEffect(() => { fetch("http://localhost:5000/api/getPollDetails", { - headers: {"content-type": "application/json"}, + headers: new Headers({ + 'Authorization': 'Bearer ' + oidcUser.access_token, + "content-type": "application/json" + }), method: "POST", body: JSON.stringify({"voteId": voteId}) }) @@ -51,7 +57,10 @@ export const Vote: React.FunctionComponent = () =>{ function buttonClick(idx:number|null) { if (idx !== null) { fetch("http://localhost:5000/api/sendVote", { - headers: {"content-type": "application/json"}, + headers: new Headers({ + 'Authorization': 'Bearer ' + oidcUser.access_token, + "content-type": "application/json" + }), method: "POST", body: JSON.stringify({"voteId": voteId, "voteChoice": idx}) }) diff --git a/package.json b/package.json index c22aab1..fae7943 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "express": "^4.17.1", "express-session": "^1.17.1", "history": "^5.0.0", + "https": "^1.0.0", "oidc-client": "^1.10.1", "passport": "^0.4.1", "passport-jwt": "^4.0.0", @@ -25,7 +26,8 @@ "react-router-dom": "^5.2.0", "react-scripts": "^3.4.3", "reactstrap": "^8.6.0", - "ts-node": "^9.0.0" + "ts-node": "^9.0.0", + "url": "^0.11.0" }, "scripts": { "start": "concurrently \"ts-node ./service/index.ts\" \"cd client && react-scripts start\"", diff --git a/service/index.ts b/service/index.ts index d521919..8bca3b6 100644 --- a/service/index.ts +++ b/service/index.ts @@ -6,60 +6,57 @@ dotenv.config() const passport = require('passport'); +const https = require('https') +var url = require("url"); var JwtStrategy = require('passport-jwt').Strategy, ExtractJwt = require('passport-jwt').ExtractJwt; -var opts = { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: process.env.CLIENT_SECRET, - issuer: 'https://sso.csh.rit.edu/auth/realms/csh' + + +const options = { + hostname: 'sso.csh.rit.edu', + path: '/auth/realms/csh/.well-known/openid-configuration' } -passport.use(new JwtStrategy(opts, function(jwt_payload, done) { - console.log(jwt_payload) - return done(); - })); - - -// passport.use( -// new Strategy( -// { -// issuer: , -// authorizationURL: -// 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/auth', -// tokenURL: -// 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/token', -// userInfoURL: -// 'https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/userinfo', -// clientID: process.env.CLIENT_ID, -// clientSecret: process.env.CLIENT_SECRET }, -// (accessToken, refreshToken, profile, cb) => { -// return cb(null, profile); -// } -// ) -// ); + + +https.get(options, res => { + res.setEncoding('utf8'); + res.on('data', function (chunk) { + const jwks_uri = JSON.parse(chunk).jwks_uri; + https.get({hostname: url.parse(jwks_uri).hostname, path: url.parse(jwks_uri).pathname}, res => { + res.setEncoding('utf8'); + res.on('data', function (chunk) { + let secretOrKey = JSON.parse(chunk).keys[0].x5c[0] + secretOrKey = secretOrKey.match(/.{1,64}/g).join('\n'); + secretOrKey = `-----BEGIN CERTIFICATE-----\n${secretOrKey}\n-----END CERTIFICATE-----\n`; + var opts = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: secretOrKey, + issuer: 'https://sso.csh.rit.edu/auth/realms/csh' + } + passport.use(new JwtStrategy(opts, function(jwt_payload, done) { + return done(null, jwt_payload); + })); + + }) + }) + + + }); +}) // Configure Passport authenticated session persistence. -// passport.serializeUser((user, cb) => { -// cb(null, user); -// }); +passport.serializeUser((user, cb) => { + cb(null, user); +}); -// passport.deserializeUser((obj, cb) => { -// cb(null, obj); -// }); +passport.deserializeUser((obj, cb) => { + cb(null, obj); +}); const app = express(); - -// app.use( -// require('express-session')({ -// secret:" process.env.EXPRESS_SESSION_SECRET", -// resave: true, -// saveUninitialized: true, -// }) -// ); -// // Initialize Passport and restore authentication state from the session. app.use(passport.initialize()); -// app.use(passport.session()); app.use(express.json()); app.use(express.static(path.join(__dirname, "/../build"))); @@ -71,11 +68,11 @@ let currentPolls = [{"id": 1, "name": "eat a whole cake conditional", "voteOptio // Returns list of all current polls -app.get("/api/getCurrentPolls", passport.authenticate('jwt', { session: false }), (req,res) => { +app.get("/api/getCurrentPolls", passport.authenticate('jwt'), (req,res) => { res.json(currentPolls); }); -app.post("/api/getPollDetails", (req,res) => { +app.post("/api/getPollDetails", passport.authenticate('jwt'), (req,res) => { const poll = currentPolls.find(x => x.id === parseInt(req.body.voteId)); if (poll) { res.json(poll) @@ -85,12 +82,12 @@ app.post("/api/getPollDetails", (req,res) => { }); // Send in a client's vote, called from any voting screen -app.post("/api/sendVote", (req,res) => { +app.post("/api/sendVote", passport.authenticate('jwt'), (req,res) => { res.status(204).send(); }); // Initialize a poll/vote, called from eval's init screen -app.post("/api/initializePoll", (req,res) => { +app.post("/api/initializePoll", passport.authenticate('jwt'), (req,res) => { // TODO - the id should actually be generated by putting it in the db // for now it's just the idx in the list since we're not doing db things yet const newPoll = { @@ -103,12 +100,12 @@ app.post("/api/initializePoll", (req,res) => { }); // get the count without ending the poll -app.get("/api/getCount", (req,res) => { +app.get("/api/getCount", passport.authenticate('jwt'), (req,res) => { res.json({"countYes": 1, "countNo": 2, "countAbstain": 0, "totalCount": 3}); }); // end the poll and get the final results, called from evals view -app.get("/api/endPoll", (req,res) => { +app.get("/api/endPoll", passport.authenticate('jwt'), (req,res) => { res.json({"countYes": 1, "countNo": 2, "countAbstain": 0, "totalCount": 3}); }); /** From f455b68dd6558e7d253294482f1731a7a461e1a2 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Fri, 30 Oct 2020 11:41:49 -0400 Subject: [PATCH 07/12] add group scope --- client/src/config.ts | 2 +- service/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/config.ts b/client/src/config.ts index 3c77416..169c694 100644 --- a/client/src/config.ts +++ b/client/src/config.ts @@ -5,7 +5,7 @@ const configuration = { }/authentication/callback`, response_type: "code", post_logout_redirect_uri: "http://localhost:3000/", - scope: "openid profile email offline_access", + scope: "openid profile email offline_access groups", authority: process.env.REACT_APP_SSO_AUTHORITY, silent_redirect_uri: `${window.location.protocol}//${ window.location.hostname diff --git a/service/index.ts b/service/index.ts index 8bca3b6..f627da7 100644 --- a/service/index.ts +++ b/service/index.ts @@ -1,3 +1,5 @@ +//@ts-nocheck + import * as express from "express"; import * as path from "path"; import * as cors from "cors"; @@ -39,8 +41,6 @@ https.get(options, res => { }) }) - - }); }) From 8b93b03207b614e590a39ddc109cb063578ad806 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Fri, 30 Oct 2020 12:49:15 -0400 Subject: [PATCH 08/12] get groups --- service/index.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/service/index.ts b/service/index.ts index f627da7..9ccbc28 100644 --- a/service/index.ts +++ b/service/index.ts @@ -4,12 +4,14 @@ import * as express from "express"; import * as path from "path"; import * as cors from "cors"; import * as dotenv from 'dotenv'; + dotenv.config() const passport = require('passport'); const https = require('https') var url = require("url"); + var JwtStrategy = require('passport-jwt').Strategy, ExtractJwt = require('passport-jwt').ExtractJwt; @@ -69,7 +71,22 @@ let currentPolls = [{"id": 1, "name": "eat a whole cake conditional", "voteOptio // Returns list of all current polls app.get("/api/getCurrentPolls", passport.authenticate('jwt'), (req,res) => { - res.json(currentPolls); + https.get({hostname: "sso.csh.rit.edu", + path: "/auth/realms/csh/protocol/openid-connect/userinfo", + headers: {"Authorization": req.headers.authorization} + }, infoRes => { + infoRes.setEncoding('utf8'); + infoRes.on('data', function (chunk) { + const groups = JSON.parse(chunk).groups; + if (!groups.includes("active")) { + res.status(403).send(); + } else if (groups.includes("10weeks") || groups.includes("fall-coop")) { + res.status(403).send(); + } else { + res.json(currentPolls) + } + }); + }) }); app.post("/api/getPollDetails", passport.authenticate('jwt'), (req,res) => { From 21145acc0e9a384bccaa6ff9da7ed8bd6b3caf9c Mon Sep 17 00:00:00 2001 From: Rebecca Date: Fri, 30 Oct 2020 13:12:45 -0400 Subject: [PATCH 09/12] fix some errors --- client/src/components/Pages/Result/index.tsx | 17 ++++++++++++++--- client/src/components/Pages/Vote/index.tsx | 5 +---- service/index.ts | 3 ++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/client/src/components/Pages/Result/index.tsx b/client/src/components/Pages/Result/index.tsx index 2c541f6..5656d53 100644 --- a/client/src/components/Pages/Result/index.tsx +++ b/client/src/components/Pages/Result/index.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import { useParams, useHistory } from 'react-router-dom'; import Spinner from "../../Spinner"; import "./result.css"; +import { useReactOidc } from "@axa-fr/react-oidc-context"; type RouteParams = { voteId: string @@ -21,12 +22,16 @@ export const Result: React.FunctionComponent = () =>{ const [ended, setEnded] = useState(false); const evals = true; let history = useHistory(); + const { oidcUser } = useReactOidc(); useEffect(() => { const interval = setInterval(() => { if(!ended) { fetch("http://localhost:5000/api/getCount", { - headers: {"content-type": "application/json"}, + headers: new Headers({ + 'Authorization': 'Bearer ' + oidcUser.access_token, + "content-type": "application/json" + }), method: "POST", body: JSON.stringify({"voteId": voteId}) }) @@ -57,7 +62,10 @@ export const Result: React.FunctionComponent = () =>{ useEffect(() => { fetch("http://localhost:5000/api/getCount", { - headers: {"content-type": "application/json"}, + headers: new Headers({ + 'Authorization': 'Bearer ' + oidcUser.access_token, + "content-type": "application/json" + }), method: "POST", body: JSON.stringify({"voteId": voteId}) }) @@ -87,7 +95,10 @@ export const Result: React.FunctionComponent = () =>{ if(window.confirm("End Voting?")){ setLoading(true) fetch("http://localhost:5000/api/endPoll", { - headers: {"content-type": "application/json"}, + headers: new Headers({ + 'Authorization': 'Bearer ' + oidcUser.access_token, + "content-type": "application/json" + }), method: "POST", body: JSON.stringify({"voteId": voteId}) }) diff --git a/client/src/components/Pages/Vote/index.tsx b/client/src/components/Pages/Vote/index.tsx index 516983e..4e30b9e 100644 --- a/client/src/components/Pages/Vote/index.tsx +++ b/client/src/components/Pages/Vote/index.tsx @@ -74,11 +74,8 @@ export const Vote: React.FunctionComponent = () =>{ } }) .catch((error) => { + window.alert("An error occurred (You may have already voted)") history.push("/result/" + voteId) - - // setLoading(false); - // setError(true); - // console.log(error); }); } } diff --git a/service/index.ts b/service/index.ts index 68c6193..a732bae 100644 --- a/service/index.ts +++ b/service/index.ts @@ -111,7 +111,7 @@ app.get("/api/getCurrentPolls", passport.authenticate('jwt'), (req,res) => { }); app.post("/api/getPollDetails", passport.authenticate('jwt'), (req,res) => { - const poll = currentPolls.find(x => x.id === parseInt(req.body.voteId)); + const poll = currentPolls.find(x => x._id === req.body.voteId); if (poll) { res.json(poll) } else { @@ -213,6 +213,7 @@ app.post("/api/endPoll", passport.authenticate('jwt'), (req,res) => { }); res.json(count); }); + }); app.get('*', (req,res) =>{ res.sendFile(path.join(__dirname+"/../build/index.html")); From 3ec6c8270b44b155791196745450b607d43841e8 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Fri, 30 Oct 2020 14:55:37 -0400 Subject: [PATCH 10/12] make everything work with groups --- client/src/components/NavBar/NavBar.tsx | 10 +- client/src/components/NavBar/Profile.tsx | 1 - client/src/components/Pages/Create/index.tsx | 3 +- client/src/components/Pages/Result/index.tsx | 9 +- client/src/components/Pages/Vote/index.tsx | 3 +- service/database.ts | 39 --- service/index.ts | 235 ++++++++++++------- 7 files changed, 161 insertions(+), 139 deletions(-) delete mode 100644 service/database.ts diff --git a/client/src/components/NavBar/NavBar.tsx b/client/src/components/NavBar/NavBar.tsx index 6a8ca3b..89c7627 100644 --- a/client/src/components/NavBar/NavBar.tsx +++ b/client/src/components/NavBar/NavBar.tsx @@ -9,6 +9,7 @@ import { } from "reactstrap"; import { NavLink } from "react-router-dom"; import Profile from "./Profile"; +import { useReactOidc } from "@axa-fr/react-oidc-context"; const NavBar: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); @@ -16,8 +17,13 @@ const NavBar: React.FunctionComponent = () => { const toggle = () => { setIsOpen(!isOpen); }; + const { oidcUser } = useReactOidc(); + let evals = false; + if (oidcUser) { + evals = oidcUser.profile.groups.includes("eboard-evaluations"); + + } - const isEvals = true; return (
    @@ -34,7 +40,7 @@ const NavBar: React.FunctionComponent = () => { Home - {isEvals ? + {evals ? Create diff --git a/client/src/components/NavBar/Profile.tsx b/client/src/components/NavBar/Profile.tsx index 4bd0ef7..ff18536 100644 --- a/client/src/components/NavBar/Profile.tsx +++ b/client/src/components/NavBar/Profile.tsx @@ -11,7 +11,6 @@ const Profile: React.FunctionComponent = () => { const { oidcUser, logout } = useReactOidc(); if (!oidcUser) return null; - console.log(oidcUser); const { profile: { name, preferred_username }, } = oidcUser; diff --git a/client/src/components/Pages/Create/index.tsx b/client/src/components/Pages/Create/index.tsx index 4cdff2c..ec4b65f 100644 --- a/client/src/components/Pages/Create/index.tsx +++ b/client/src/components/Pages/Create/index.tsx @@ -11,6 +11,7 @@ export const Create: React.FunctionComponent = () =>{ const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const { oidcUser } = useReactOidc(); + const evals = oidcUser.profile.groups.includes("eboard-evaluations"); let history = useHistory(); @@ -86,7 +87,7 @@ export const Create: React.FunctionComponent = () =>{ return( loading ? : - error ?
    Something went wrong : (
    : + error || !evals ?
    Something went wrong : (
    :
    Create Vote
    diff --git a/client/src/components/Pages/Result/index.tsx b/client/src/components/Pages/Result/index.tsx index 5656d53..b3b5979 100644 --- a/client/src/components/Pages/Result/index.tsx +++ b/client/src/components/Pages/Result/index.tsx @@ -20,10 +20,9 @@ export const Result: React.FunctionComponent = () =>{ const [poll, setPoll] = useState(undefined); const [ended, setEnded] = useState(false); - const evals = true; let history = useHistory(); const { oidcUser } = useReactOidc(); - + const evals = oidcUser.profile.groups.includes("eboard-evaluations"); useEffect(() => { const interval = setInterval(() => { if(!ended) { @@ -48,12 +47,10 @@ export const Result: React.FunctionComponent = () =>{ .then((result) => { setLoading(false); setPoll(result) - console.log(result) }, (error) => { setLoading(false); setError(true); - console.log(error); }); } }, 10000); @@ -82,12 +79,10 @@ export const Result: React.FunctionComponent = () =>{ .then((result) => { setLoading(false); setPoll(result) - console.log(result) }, (error) => { setLoading(false); setError(true); - console.log(error); }); }, [voteId]) @@ -116,12 +111,10 @@ export const Result: React.FunctionComponent = () =>{ setLoading(false); setPoll(result) setEnded(true) - console.log(result) }, (error) => { setLoading(false); setError(true); - console.log(error); }); } } diff --git a/client/src/components/Pages/Vote/index.tsx b/client/src/components/Pages/Vote/index.tsx index 4e30b9e..60defe9 100644 --- a/client/src/components/Pages/Vote/index.tsx +++ b/client/src/components/Pages/Vote/index.tsx @@ -50,7 +50,6 @@ export const Vote: React.FunctionComponent = () =>{ (error) => { setLoading(false); setError(true); - console.log(error); }); }, [voteId]) @@ -62,7 +61,7 @@ export const Vote: React.FunctionComponent = () =>{ "content-type": "application/json" }), method: "POST", - body: JSON.stringify({"voteId": voteId, "voteChoice": idx}) + body: JSON.stringify({"voteId": voteId, "voteChoice": poll?.choices[idx]}) }) .then((res) => { switch(res.status) { diff --git a/service/database.ts b/service/database.ts deleted file mode 100644 index 5414b72..0000000 --- a/service/database.ts +++ /dev/null @@ -1,39 +0,0 @@ -var ObjectID = require('mongodb').ObjectID; - -export function insertPoll(db, poll) { - Promise.resolve(db).then((res) =>{ - const collection = res.collection("Polls") - collection.insert(poll, function(err,docsInserted){ - console.log(poll._id); - return(poll._id) - }); - }) - } - - -export async function insertVote(db) { - const vote = { - time: new Date(), - userName: "rebeccas", - choice: "abstain", - poll: ObjectID.createFromHexString("5f9b2d5d601e1c6971430638") - } - const findData = { - userName: "rebeccas", - poll: ObjectID.createFromHexString("5f9b2d5d601e1c6971430638") - } - await Promise.resolve(db); - const collection = db.collection("Votes") - collection.findOne(findData).then(res => { - if(res) { - throw new Error("Already Voted"); - } else { - collection.insert(vote, function(err,docsInserted){ - console.log(docsInserted); - }); - } - }).catch(() => { - return false; - }); - return true; -} diff --git a/service/index.ts b/service/index.ts index a732bae..b2ae7a6 100644 --- a/service/index.ts +++ b/service/index.ts @@ -3,10 +3,10 @@ import * as express from "express"; import * as path from "path"; import * as cors from "cors"; +import * as dotenv from 'dotenv'; var MongoClient = require('mongodb').MongoClient -import * as dotenv from 'dotenv'; var ObjectID = require('mongodb').ObjectID; @@ -83,11 +83,6 @@ let currentPolls = [ _id: "5f9b2d5d601e1c6971430638" } ]; -const sampleCount = { - "name": "Potate's Vote", - "results": {"option1": 1, "option2": 2, "option3": 0} -} - app.use(express.static(path.join(__dirname, "/../build"))); // Returns list of all current polls @@ -101,118 +96,186 @@ app.get("/api/getCurrentPolls", passport.authenticate('jwt'), (req,res) => { const groups = JSON.parse(chunk).groups; if (!groups.includes("active")) { res.status(403).send(); - } else if (groups.includes("10weeks") || groups.includes("fall-coop")) { + } else if (groups.includes("10weeks") || groups.includes("fall_coop")) { res.status(403).send(); } else { res.json(currentPolls) } }); - }) + }); }); app.post("/api/getPollDetails", passport.authenticate('jwt'), (req,res) => { const poll = currentPolls.find(x => x._id === req.body.voteId); - if (poll) { - res.json(poll) - } else { - res.status(404).send(); - } + https.get({hostname: "sso.csh.rit.edu", + path: "/auth/realms/csh/protocol/openid-connect/userinfo", + headers: {"Authorization": req.headers.authorization} + }, infoRes => { + infoRes.setEncoding('utf8'); + infoRes.on('data', function (chunk) { + const groups = JSON.parse(chunk).groups; + if (!groups.includes("active")) { + res.status(403).send(); + } else if (groups.includes("10weeks") || groups.includes("fall_coop")) { + res.status(403).send(); + } else { + if (poll) { + res.json(poll) + } else { + res.status(404).send(); + } + } + }); + }); }); // Send in a client's vote, called from any voting screen app.post("/api/sendVote", passport.authenticate('jwt'), (req,res) => { - //TODO - replace this with the actual vote request - //need to get the userName from oidc - const vote = { - time: new Date(), - userName: "rebeccas", - choice: "abstain", - poll: ObjectID.createFromHexString("5f9b2d5d601e1c6971430638") - } - const findData = { - userName: "rebeccas", - poll: ObjectID.createFromHexString("5f9b2d5d601e1c6971430638") - } - - - const collection = db.collection("Votes"); - - collection.findOne(findData).then(hasVoted => { - if(hasVoted) { - res.status(400).send(); - } else { - collection.insert(vote, function(err,docsInserted){ - res.status(204).send(); - }); - } - }) + https.get({hostname: "sso.csh.rit.edu", + path: "/auth/realms/csh/protocol/openid-connect/userinfo", + headers: {"Authorization": req.headers.authorization} + }, infoRes => { + infoRes.setEncoding('utf8'); + infoRes.on('data', function (chunk) { + const groups = JSON.parse(chunk).groups; + if (!groups.includes("active")) { + res.status(403).send(); + } else if (groups.includes("10weeks") || groups.includes("fall_coop")) { + res.status(403).send(); + } else { + const vote = { + time: new Date(), + userName: JSON.parse(chunk).preferred_username, + choice: req.body.voteChoice, + poll: ObjectID.createFromHexString(req.body.voteId) + } + const findData = { + userName: vote.userName, + poll: vote.poll + } + const collection = db.collection("Votes"); + + collection.findOne(findData).then(hasVoted => { + if(hasVoted) { + res.status(400).send(); + } else { + collection.insert(vote, function(err,docsInserted){ + res.status(204).send(); + }); + } + }) + } + }); + }); }); // Initialize a poll/vote, called from eval's init screen app.post("/api/initializePoll", passport.authenticate('jwt'), (req,res) => { - // TODO - the id should actually be generated by putting it in the db - // for now it's just the idx in the list since we're not doing db things yet - const newPoll = { - "_id": null, - "title": req.body.title, - "choices": req.body.options, - "type": req.body.type, - "time": new Date() - } - const collection = db.collection("Polls") - collection.insert(newPoll, function(err,docsInserted){ - newPoll._id = newPoll._id.toHexString(); + https.get({hostname: "sso.csh.rit.edu", + path: "/auth/realms/csh/protocol/openid-connect/userinfo", + headers: {"Authorization": req.headers.authorization} + }, infoRes => { + infoRes.setEncoding('utf8'); + infoRes.on('data', function (chunk) { + const groups = JSON.parse(chunk).groups; + if (!groups.includes("eboard-evaluations")) { + res.status(403).send(); + } else { + const newPoll = { + "_id": null, + "title": req.body.title, + "choices": req.body.options, + "type": req.body.type, + "time": new Date() + } + const collection = db.collection("Polls") + collection.insert(newPoll, function(err,docsInserted){ + newPoll._id = newPoll._id.toHexString(); + currentPolls.push(newPoll); + res.json({"pollId": newPoll._id}); + }); + } }); - currentPolls.push(newPoll); - res.json({"pollId": newPoll._id}); + }); }); // get the count without ending the poll app.post("/api/getCount",passport.authenticate('jwt'), (req,res) => { - const count = {name: "", results:{}} - const pollCollection = db.collection("Polls") - pollCollection.findOne({"_id": ObjectID.createFromHexString(req.body.voteId)}).then(poll => { - count.name = poll.title; - }); - const collection = db.collection("Votes") - collection.find({"poll": ObjectID.createFromHexString(req.body.voteId)}).toArray(function(err, result) { - if (err) throw err; - result.forEach(element => { - const choice = element.choice; - if (count.results[choice]) { - count.results[choice] += 1; + https.get({hostname: "sso.csh.rit.edu", + path: "/auth/realms/csh/protocol/openid-connect/userinfo", + headers: {"Authorization": req.headers.authorization} + }, infoRes => { + infoRes.setEncoding('utf8'); + infoRes.on('data', function (chunk) { + const groups = JSON.parse(chunk).groups; + if (!groups.includes("active")) { + res.status(403).send(); + } else if (groups.includes("10weeks") || groups.includes("fall_coop")) { + res.status(403).send(); } else { - count.results[choice] = 1; + const count = {name: "", results:{}} + const pollCollection = db.collection("Polls") + pollCollection.findOne({"_id": ObjectID.createFromHexString(req.body.voteId)}).then(poll => { + count.name = poll.title; + }); + const collection = db.collection("Votes") + collection.find({"poll": ObjectID.createFromHexString(req.body.voteId)}).toArray(function(err, result) { + if (err) throw err; + result.forEach(element => { + const choice = element.choice; + if (count.results[choice]) { + count.results[choice] += 1; + } else { + count.results[choice] = 1; + } + }); + res.json(count); + }); } }); - res.json(count); - }); + }); + + }); // end the poll and get the final results, called from evals view app.post("/api/endPoll", passport.authenticate('jwt'), (req,res) => { - var removeIndex = currentPolls.map(item => item._id) - .indexOf(req.body.voteId); - ~removeIndex && currentPolls.splice(removeIndex, 1); - - const count = {name: "", results:{}} - const pollCollection = db.collection("Polls") - pollCollection.findOne({"_id": ObjectID.createFromHexString(req.body.voteId)}).then(poll => { - count.name = poll.title; - }); - const collection = db.collection("Votes") - collection.find({"poll": ObjectID.createFromHexString(req.body.voteId)}).toArray(function(err, result) { - if (err) throw err; - result.forEach(element => { - const choice = element.choice; - if (count.results[choice]) { - count.results[choice] += 1; + https.get({hostname: "sso.csh.rit.edu", + path: "/auth/realms/csh/protocol/openid-connect/userinfo", + headers: {"Authorization": req.headers.authorization} + }, infoRes => { + infoRes.setEncoding('utf8'); + infoRes.on('data', function (chunk) { + const groups = JSON.parse(chunk).groups; + if (!groups.includes("eboard-evaluations")) { + res.status(403).send(); } else { - count.results[choice] = 1; + let removeIndex = currentPolls.map(item => item._id) + .indexOf(req.body.voteId); + ~removeIndex && currentPolls.splice(removeIndex, 1); + + const count = {name: "", results:{}} + const pollCollection = db.collection("Polls") + pollCollection.findOne({"_id": ObjectID.createFromHexString(req.body.voteId)}).then(poll => { + count.name = poll.title; + }); + const collection = db.collection("Votes") + collection.find({"poll": ObjectID.createFromHexString(req.body.voteId)}).toArray(function(err, result) { + if (err) throw err; + result.forEach(element => { + const choice = element.choice; + if (count.results[choice]) { + count.results[choice] += 1; + } else { + count.results[choice] = 1; + } + }); + res.json(count); + }); } }); - res.json(count); - }); + }); + }); app.get('*', (req,res) =>{ From a44eb2cfd43bcb96bb5616267768798b607b2d36 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Fri, 30 Oct 2020 15:03:23 -0400 Subject: [PATCH 11/12] fix build --- client/src/components/Pages/Create/index.tsx | 1 - client/src/components/Pages/Home/index.tsx | 3 +-- client/src/components/Pages/Result/index.tsx | 4 ++-- client/src/components/Pages/Vote/index.tsx | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/client/src/components/Pages/Create/index.tsx b/client/src/components/Pages/Create/index.tsx index ec4b65f..04aa511 100644 --- a/client/src/components/Pages/Create/index.tsx +++ b/client/src/components/Pages/Create/index.tsx @@ -81,7 +81,6 @@ export const Create: React.FunctionComponent = () =>{ .catch((error) => { setLoading(false); setError(true); - console.log(error); }); } diff --git a/client/src/components/Pages/Home/index.tsx b/client/src/components/Pages/Home/index.tsx index 9588c74..ee6606e 100644 --- a/client/src/components/Pages/Home/index.tsx +++ b/client/src/components/Pages/Home/index.tsx @@ -20,11 +20,10 @@ export const Home: React.FunctionComponent = () =>{ setCurrentPolls(result); }, (error) => { - console.log(error) setLoaded(true); setError(error); }); - }, []) + }, [oidcUser.access_token ]) return ( error ? diff --git a/client/src/components/Pages/Result/index.tsx b/client/src/components/Pages/Result/index.tsx index b3b5979..85ef60d 100644 --- a/client/src/components/Pages/Result/index.tsx +++ b/client/src/components/Pages/Result/index.tsx @@ -55,7 +55,7 @@ export const Result: React.FunctionComponent = () =>{ } }, 10000); return () => clearInterval(interval); - }, [voteId, ended]); + }, [voteId, ended, oidcUser.access_token]); useEffect(() => { fetch("http://localhost:5000/api/getCount", { @@ -84,7 +84,7 @@ export const Result: React.FunctionComponent = () =>{ setLoading(false); setError(true); }); - }, [voteId]) + }, [voteId, oidcUser.access_token]) function endVoting() { if(window.confirm("End Voting?")){ diff --git a/client/src/components/Pages/Vote/index.tsx b/client/src/components/Pages/Vote/index.tsx index 60defe9..bfb3fb1 100644 --- a/client/src/components/Pages/Vote/index.tsx +++ b/client/src/components/Pages/Vote/index.tsx @@ -51,7 +51,7 @@ export const Vote: React.FunctionComponent = () =>{ setLoading(false); setError(true); }); - }, [voteId]) + }, [voteId, oidcUser.access_token]) function buttonClick(idx:number|null) { if (idx !== null) { From d32f3b09f33d1435c194671ea020cd5712327983 Mon Sep 17 00:00:00 2001 From: Rebecca Date: Fri, 30 Oct 2020 15:10:48 -0400 Subject: [PATCH 12/12] delete outdated test to hopefully fix build --- client/src/components/Pages/Home/index.test.tsx | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 client/src/components/Pages/Home/index.test.tsx diff --git a/client/src/components/Pages/Home/index.test.tsx b/client/src/components/Pages/Home/index.test.tsx deleted file mode 100644 index 5fcee4e..0000000 --- a/client/src/components/Pages/Home/index.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; -import { render } from "@testing-library/react"; -import Home from "."; - -test("renders main app page", () => { - const { getByTestId } = render(); - const divElement = getByTestId('spinner'); - expect(divElement).toBeDefined(); -});