├── .gitignore ├── README.md ├── go.mod ├── go.sum ├── main.go ├── static ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Home.js │ └── LoggedIn.js │ ├── history.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── react-auth0-spa.js │ ├── serviceWorker.js │ └── setupTests.js └── views └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | static/src/auth_config.json 3 | # dependencies 4 | static/node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Authentication in Golang with JWTs 2 | 3 | ![Auth0 Go React](https://cdn.auth0.com/blog/go-auth/go-react-app-final.png) 4 | 5 | This application will show you how to build and secure a mock voting application. You'll create an API with Go and a frontend with React that will consume your API. You'll secure your API using Auth0 so that only authenticated users can access and vote on products. 6 | 7 | **🚀 Read the full tutorial here**: 8 | 9 | [Authentication in Golang with JWTs](https://auth0.com/blog/authentication-in-golang) 10 | 11 | ## Technology 12 | 13 | This demo uses: 14 | 15 | - [Go v1.14](https://golang.org/) 16 | - [React v16.13](https://reactjs.org/) 17 | - [Auth0](https://auth0.com/) 18 | 19 | Make sure you have [Go installed](https://golang.org/doc/install) before running the demo. 20 | 21 | ## Running the demo 22 | 23 | **Clone and get dependencies** 24 | 25 | * Clone the repo with `git clone https://github.com/auth0-blog/go-react-vr-auth` 26 | * Switch to the directory that holds the repo `cd go-react-vr-auth` 27 | * Run `go get` to grab the dependencies 28 | 29 | **Auth0 Setup** 30 | 31 | If you do not have an Auth0 account, [sign up](https://auth0.com/signup) for one now. 32 | 33 | * Navigate to the Auth0 [management dashboard](https://manage.auth0.com/) and click on **Create Application**. Name your application anything you'd like, select "Single Page Web Applications", and click **Create**. 34 | * Click on **Settings** and fill in **Allowed Callback URLs**, **Allowed Logout URLs**, and **Allowed Web Origins** with `http://localhost:3000` and then press **Save changes**. 35 | * Back in your code, create a new file to store some of these variables: 36 | 37 | ```bash 38 | touch static/src/auth_config.json 39 | ``` 40 | 41 | Then fill it in with: 42 | 43 | ```json 44 | { 45 | "domain": "YOUR_DOMAIN", 46 | "clientId": "YOUR_CLIENT_ID" 47 | } 48 | ``` 49 | 50 | Update this with your own credentials. You can find them in your Auth0 dashboard application page that you just created. 51 | 52 | * Now set up the Go API with Auth0. In the dashboard, click on **APIs** in the sidebar and then click **Create API**. 53 | * Give it any name you'd like. For identifier, this can be anything, but for naming conventions we recommend a URL format (doesn't have to be a real URL). Leave Signing Algorithm as is and click **Create**. 54 | * Update your `main.go` file with the required values from the Auth0 dashboard. There are 3 different spots that need to be updated: 55 | 56 | ```go 57 | // main.go 58 | // ... 59 | aud := "YOUR_API_IDENTIFIER" 60 | // ... 61 | iss := "https://YOUR_DOMAIN/" 62 | // ... 63 | resp, err := http.Get("https://YOUR_DOMAIN/.well-known/jwks.json") 64 | // ... 65 | ``` 66 | 67 | Your **Identifier** is listed on the Settings tab of the API you just created in the dashboard. To find `YOUR_DOMAIN`, click on the **Quick Start** tab and scroll down to the code snippet. Copy the value for `options.Authority` and use that to replace `YOUR_DOMAIN`. Make sure you keep the trailing slash for `iss` in `main.go`. 68 | 69 | **Run React** 70 | 71 | * Open a new terminal tab and switch to the React directory and then pull in the required dependencies 72 | 73 | ```bash 74 | cd static 75 | npm install 76 | ``` 77 | 78 | * Start your React application 79 | 80 | ```bash 81 | npm start 82 | ``` 83 | 84 | **Run Go** 85 | 86 | * In a separate terminal tab, grab the dependencies 87 | 88 | ```bash 89 | go get 90 | ``` 91 | 92 | * Build and run your Go application: 93 | 94 | ```bash 95 | go run . 96 | ``` 97 | 98 | 💥 Navigate to [`http://localhost:3000`](http://localhost:3000) to see it in action! 💥 99 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/auth0-blog/go-vr-auth 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/gorilla/handlers v1.4.2 9 | github.com/gorilla/mux v1.7.4 10 | github.com/rs/cors v1.7.0 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b h1:CvoEHGmxWl5kONC5icxwqV899dkf4VjOScbxLpllEnw= 2 | github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= 3 | github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= 4 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 5 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 6 | github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= 7 | github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= 8 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= 9 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 10 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 11 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 12 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Import our dependencies. We'll use the standard HTTP library as well as the gorilla router for this app 4 | import ( 5 | "encoding/json" 6 | "errors" 7 | "github.com/auth0/go-jwt-middleware" 8 | "github.com/dgrijalva/jwt-go" 9 | "github.com/gorilla/mux" 10 | "github.com/rs/cors" 11 | "net/http" 12 | ) 13 | 14 | type Response struct { 15 | Message string `json:"message"` 16 | } 17 | 18 | type Jwks struct { 19 | Keys []JSONWebKeys `json:"keys"` 20 | } 21 | 22 | type JSONWebKeys struct { 23 | Kty string `json:"kty"` 24 | Kid string `json:"kid"` 25 | Use string `json:"use"` 26 | N string `json:"n"` 27 | E string `json:"e"` 28 | X5c []string `json:"x5c"` 29 | } 30 | 31 | /* We will first create a new type called Product 32 | This type will contain information about VR experiences */ 33 | type Product struct { 34 | Id int 35 | Name string 36 | Slug string 37 | Description string 38 | } 39 | 40 | var products = []Product{ 41 | Product{Id: 1, Name: "World of Authcraft", Slug: "world-of-authcraft", Description: "Battle bugs and protect yourself from invaders while you explore a scary world with no security"}, 42 | Product{Id: 2, Name: "Ocean Explorer", Slug: "ocean-explorer", Description: "Explore the depths of the sea in this one of a kind underwater experience"}, 43 | Product{Id: 3, Name: "Dinosaur Park", Slug: "dinosaur-park", Description: "Go back 65 million years in the past and ride a T-Rex"}, 44 | Product{Id: 4, Name: "Cars VR", Slug: "cars-vr", Description: "Get behind the wheel of the fastest cars in the world."}, 45 | Product{Id: 5, Name: "Robin Hood", Slug: "robin-hood", Description: "Pick up the bow and arrow and master the art of archery"}, 46 | Product{Id: 6, Name: "Real World VR", Slug: "real-world-vr", Description: "Explore the seven wonders of the world in VR"}, 47 | } 48 | 49 | func main() { 50 | 51 | jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ 52 | ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { 53 | // Verify 'aud' claim 54 | aud := "YOUR_API_IDENTIFIER" 55 | checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) 56 | if !checkAud { 57 | return token, errors.New("Invalid audience.") 58 | } 59 | // Verify 'iss' claim 60 | iss := "https://YOUR_DOMAIN/" 61 | checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) 62 | if !checkIss { 63 | return token, errors.New("Invalid issuer.") 64 | } 65 | 66 | cert, err := getPemCert(token) 67 | if err != nil { 68 | panic(err.Error()) 69 | } 70 | 71 | result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) 72 | return result, nil 73 | }, 74 | SigningMethod: jwt.SigningMethodRS256, 75 | }) 76 | 77 | r := mux.NewRouter() 78 | 79 | r.Handle("/", http.FileServer(http.Dir("./views/"))) 80 | r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) 81 | 82 | r.Handle("/products", jwtMiddleware.Handler(ProductsHandler)).Methods("GET") 83 | r.Handle("/products/{slug}/feedback", jwtMiddleware.Handler(AddFeedbackHandler)).Methods("POST") 84 | 85 | // For dev only - Set up CORS so React client can consume our API 86 | corsWrapper := cors.New(cors.Options{ 87 | AllowedMethods: []string{"GET", "POST"}, 88 | AllowedHeaders: []string{"Content-Type", "Origin", "Accept", "*"}, 89 | }) 90 | 91 | http.ListenAndServe(":8080", corsWrapper.Handler(r)) 92 | } 93 | 94 | var ProductsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 95 | payload, _ := json.Marshal(products) 96 | 97 | w.Header().Set("Content-Type", "application/json") 98 | w.Write([]byte(payload)) 99 | }) 100 | 101 | var AddFeedbackHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 102 | var product Product 103 | vars := mux.Vars(r) 104 | slug := vars["slug"] 105 | 106 | for _, p := range products { 107 | if p.Slug == slug { 108 | product = p 109 | } 110 | } 111 | 112 | w.Header().Set("Content-Type", "application/json") 113 | if product.Slug != "" { 114 | payload, _ := json.Marshal(product) 115 | w.Write([]byte(payload)) 116 | } else { 117 | http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 118 | } 119 | }) 120 | 121 | func getPemCert(token *jwt.Token) (string, error) { 122 | cert := "" 123 | resp, err := http.Get("https://YOUR-DOMAIN.com/.well-known/jwks.json") 124 | 125 | if err != nil { 126 | return cert, err 127 | } 128 | defer resp.Body.Close() 129 | 130 | var jwks = Jwks{} 131 | err = json.NewDecoder(resp.Body).Decode(&jwks) 132 | 133 | if err != nil { 134 | return cert, err 135 | } 136 | 137 | for k, _ := range jwks.Keys { 138 | if token.Header["kid"] == jwks.Keys[k].Kid { 139 | cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----" 140 | } 141 | } 142 | 143 | if cert == "" { 144 | err := errors.New("Unable to find appropriate key.") 145 | return cert, err 146 | } 147 | 148 | return cert, nil 149 | } 150 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /static/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@auth0/auth0-spa-js": "^1.8.1", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.5.0", 9 | "@testing-library/user-event": "^7.2.1", 10 | "bootstrap": "^4.4.1", 11 | "react": "^16.13.1", 12 | "react-dom": "^16.13.1", 13 | "react-icons": "^3.10.0", 14 | "react-router-dom": "^5.1.2", 15 | "react-scripts": "3.4.1" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": "react-app" 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /static/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-blog/go-react-vr/a5d99b6bd369b3549e89602df1ff373715af5f17/static/public/favicon.ico -------------------------------------------------------------------------------- /static/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-blog/go-react-vr/a5d99b6bd369b3549e89602df1ff373715af5f17/static/public/logo192.png -------------------------------------------------------------------------------- /static/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-blog/go-react-vr/a5d99b6bd369b3549e89602df1ff373715af5f17/static/public/logo512.png -------------------------------------------------------------------------------- /static/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /static/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /static/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /static/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import Home from './components/Home.js'; 4 | import LoggedIn from './components/LoggedIn.js'; 5 | import { useAuth0 } from "./react-auth0-spa"; 6 | 7 | const App = () => { 8 | const { isAuthenticated } = useAuth0(); 9 | 10 | const { loading } = useAuth0(); 11 | 12 | if (loading) { 13 | return
Loading...
; 14 | } 15 | 16 | return ( 17 |
18 | {!isAuthenticated && ( 19 | 20 | )} 21 | 22 | {isAuthenticated && } 23 |
24 | ); 25 | }; 26 | 27 | export default App; -------------------------------------------------------------------------------- /static/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /static/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { useAuth0 } from "../react-auth0-spa"; 3 | 4 | const Home = () => { 5 | const { isAuthenticated, loginWithRedirect, logout } = useAuth0(); 6 | return ( 7 | 8 |
9 |
10 |

We R VR

11 |

Provide valuable feedback to VR experience developers.

12 | {!isAuthenticated && ( 13 | 14 | )} 15 |
16 |
17 |
18 | ); 19 | }; 20 | 21 | export default Home; -------------------------------------------------------------------------------- /static/src/components/LoggedIn.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useAuth0 } from "../react-auth0-spa"; 3 | import { FiThumbsUp, FiThumbsDown } from "react-icons/fi"; 4 | 5 | const LoggedIn = () => { 6 | const [products, setProducts] = useState([]); 7 | const [voted, setVoted] = useState({ 8 | "world-of-authcraft": "", 9 | "ocean-explorer": "", 10 | "dinosaur-park": "", 11 | "cars-vr": "", 12 | "robin-hood": "", 13 | "real-world-vr": "", 14 | }); 15 | 16 | const { 17 | getTokenSilently, 18 | loading, 19 | user, 20 | logout, 21 | isAuthenticated, 22 | } = useAuth0(); 23 | 24 | useEffect(() => { 25 | const getProducts = async () => { 26 | try { 27 | const token = await getTokenSilently(); 28 | // Send a GET request to the server and add the signed in user's 29 | // access token in the Authorization header 30 | const response = await fetch("http://localhost:8080/products", { 31 | headers: { 32 | Authorization: `Bearer ${token}`, 33 | }, 34 | }); 35 | 36 | const responseData = await response.json(); 37 | 38 | setProducts(responseData); 39 | } catch (error) { 40 | console.error(error); 41 | } 42 | }; 43 | 44 | getProducts(); 45 | }, []); 46 | 47 | const vote = async (slug, type, index) => { 48 | try { 49 | const token = await getTokenSilently(); 50 | // Send a POST request to the Go server for the selected product 51 | // with the vote type 52 | const response = await fetch( 53 | `http://localhost:8080/products/${slug}/feedback`, 54 | { 55 | method: "POST", 56 | headers: { 57 | Authorization: `Bearer ${token}`, 58 | }, 59 | body: JSON.stringify({ vote: type }), 60 | } 61 | ); 62 | // Since this is just for demonstration and we're not actually 63 | // persisting this data, we'll just set the product vote status here 64 | // if the product exists 65 | if (response.ok) { 66 | setVoted({ 67 | ...voted, 68 | [slug]: [type], 69 | }); 70 | } else console.log(response.status); 71 | } catch (error) { 72 | console.error(error); 73 | } 74 | }; 75 | 76 | if (loading || !user) { 77 | return
Loading...
; 78 | } 79 | 80 | return ( 81 |
82 |
83 | {isAuthenticated && ( 84 | logout()} 87 | > 88 | Log out 89 | 90 | )} 91 |

We R VR

92 |

93 | Hi, {user.name}! Below you'll find the latest games that need 94 | feedback. Please provide honest feedback so developers can make the 95 | best games. 96 |

97 |
98 | {products.map(function (product, index) { 99 | const prodSlug = product.Slug; 100 | return ( 101 |
102 | 117 |
118 | ); 119 | })} 120 |
121 |
122 |
123 | ); 124 | }; 125 | 126 | export default LoggedIn; -------------------------------------------------------------------------------- /static/src/history.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from "history"; 2 | export default createBrowserHistory(); -------------------------------------------------------------------------------- /static/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /static/src/index.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.css'; 2 | import React from "react"; 3 | import ReactDOM from "react-dom"; 4 | import App from "./App"; 5 | import * as serviceWorker from "./serviceWorker"; 6 | import { Auth0Provider } from "./react-auth0-spa"; 7 | import config from "./auth_config.json"; 8 | import history from "./history"; 9 | 10 | // A function that routes the user to the right place 11 | // after login 12 | const onRedirectCallback = appState => { 13 | history.push( 14 | appState && appState.targetUrl 15 | ? appState.targetUrl 16 | : window.location.pathname 17 | ); 18 | }; 19 | 20 | // Wrap App in the Auth0Provider component 21 | // The domain and client_id values will be found in your Auth0 dashboard 22 | ReactDOM.render( 23 | 30 | 31 | , 32 | document.getElementById("root") 33 | ); 34 | 35 | serviceWorker.unregister(); -------------------------------------------------------------------------------- /static/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /static/src/react-auth0-spa.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from "react"; 2 | import createAuth0Client from "@auth0/auth0-spa-js"; 3 | const DEFAULT_REDIRECT_CALLBACK = () => 4 | window.history.replaceState({}, document.title, window.location.pathname); 5 | export const Auth0Context = React.createContext(); 6 | export const useAuth0 = () => useContext(Auth0Context); 7 | export const Auth0Provider = ({ 8 | children, 9 | onRedirectCallback = DEFAULT_REDIRECT_CALLBACK, 10 | ...initOptions 11 | }) => { 12 | const [isAuthenticated, setIsAuthenticated] = useState(); 13 | const [user, setUser] = useState(); 14 | const [auth0Client, setAuth0] = useState(); 15 | const [loading, setLoading] = useState(true); 16 | const [popupOpen, setPopupOpen] = useState(false); 17 | useEffect(() => { 18 | const initAuth0 = async () => { 19 | const auth0FromHook = await createAuth0Client(initOptions); 20 | setAuth0(auth0FromHook); 21 | if (window.location.search.includes("code=") && 22 | window.location.search.includes("state=")) { 23 | const { appState } = await auth0FromHook.handleRedirectCallback(); 24 | onRedirectCallback(appState); 25 | } 26 | const isAuthenticated = await auth0FromHook.isAuthenticated(); 27 | setIsAuthenticated(isAuthenticated); 28 | if (isAuthenticated) { 29 | const user = await auth0FromHook.getUser(); 30 | setUser(user); 31 | } 32 | setLoading(false); 33 | }; 34 | initAuth0(); 35 | // eslint-disable-next-line 36 | }, []); 37 | const loginWithPopup = async (params = {}) => { 38 | setPopupOpen(true); 39 | try { 40 | await auth0Client.loginWithPopup(params); 41 | } catch (error) { 42 | console.error(error); 43 | } finally { 44 | setPopupOpen(false); 45 | } 46 | const user = await auth0Client.getUser(); 47 | setUser(user); 48 | setIsAuthenticated(true); 49 | }; 50 | const handleRedirectCallback = async () => { 51 | setLoading(true); 52 | await auth0Client.handleRedirectCallback(); 53 | const user = await auth0Client.getUser(); 54 | setLoading(false); 55 | setIsAuthenticated(true); 56 | setUser(user); 57 | }; 58 | return ( 59 | auth0Client.getIdTokenClaims(...p), 68 | loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p), 69 | getTokenSilently: (...p) => auth0Client.getTokenSilently(...p), 70 | getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p), 71 | logout: (...p) => auth0Client.logout(...p) 72 | }} 73 | > 74 | {children} 75 | 76 | ); 77 | }; -------------------------------------------------------------------------------- /static/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /static/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | We R VR 4 | 5 | 6 |

Welcome to We R VR

7 | --------------------------------------------------------------------------------