mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
add vuejs-auth0-graphql sample app (#1898)
This is a sample Vue.js app with Auth0 integration. Has a simple schema with users and article tables with permissions setup. Once the user logs in to the app, the articles written by the user would be shown.
This commit is contained in:
parent
ca7d8b3df5
commit
8e78e27707
21
community/sample-apps/vuejs-auth0-graphql/.gitignore
vendored
Normal file
21
community/sample-apps/vuejs-auth0-graphql/.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw*
|
@ -0,0 +1,3 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not ie <= 8
|
@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
.git
|
36
community/sample-apps/vuejs-auth0-graphql/app/Dockerfile
Normal file
36
community/sample-apps/vuejs-auth0-graphql/app/Dockerfile
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
FROM node:10-alpine as build
|
||||||
|
|
||||||
|
RUN apk update && apk upgrade && \
|
||||||
|
apk add --no-cache bash git openssh
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json .
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
|
||||||
|
FROM node:10-alpine
|
||||||
|
|
||||||
|
RUN mkdir -p /app/dist
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=build /app/dist ./dist
|
||||||
|
COPY --from=build /app/package.json .
|
||||||
|
COPY --from=build /app/server.js .
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
114
community/sample-apps/vuejs-auth0-graphql/app/README.md
Normal file
114
community/sample-apps/vuejs-auth0-graphql/app/README.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# vuejs-auth0-graphql
|
||||||
|
|
||||||
|
This sample Vue.js app demonstrates:
|
||||||
|
|
||||||
|
- Logging in to Auth0 using Redirect Mode
|
||||||
|
- Making an authenticated graphql query fetching articles written by the logged in user
|
||||||
|
- Accessing profile information that has been provided in the ID token
|
||||||
|
- Gated content. The `/profile` route is not accessible without having first logged in
|
||||||
|
|
||||||
|
## Integrating Vue App with Auth0 and JWT authorization with Hasura GraphQL Engine
|
||||||
|
|
||||||
|
In this example, we use Hasura GraphQL engine's JWT authorization mode. We use
|
||||||
|
Auth0 as our authentication and JWT token provider.
|
||||||
|
|
||||||
|
## Create an application in Auth0
|
||||||
|
|
||||||
|
1. Create an application in Auth0 dashboard
|
||||||
|
|
||||||
|
2. In the settings of the application, add `http://localhost:3000/callback` as
|
||||||
|
"Allowed Callback URLs" and `http://localhost:3000` as "Allowed Web Origins"
|
||||||
|
|
||||||
|
## Add rules for custom JWT claims
|
||||||
|
|
||||||
|
In the Auth0 dashboard, navigate to "Rules". Add the following rules to add our custom JWT claims:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function (user, context, callback) {
|
||||||
|
const namespace = "https://hasura.io/jwt/claims";
|
||||||
|
context.idToken[namespace] =
|
||||||
|
{
|
||||||
|
'x-hasura-default-role': 'user',
|
||||||
|
// do some custom logic to decide allowed roles
|
||||||
|
'x-hasura-allowed-roles': user.email === 'admin@foobar.com' ? ['user', 'admin'] : ['user'],
|
||||||
|
'x-hasura-user-id': user.user_id
|
||||||
|
};
|
||||||
|
callback(null, user, context);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get your JWT signing certificate
|
||||||
|
|
||||||
|
Head to [https://hasura.io/jwt-config](https://hasura.io/jwt-config) and generate the config for your auth0 domain.
|
||||||
|
|
||||||
|
## Deploy Hasura GraphQL Engine
|
||||||
|
|
||||||
|
[![Deploy HGE on heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku)
|
||||||
|
|
||||||
|
After deploying, add the following environment variables to configure JWT mode:
|
||||||
|
|
||||||
|
```
|
||||||
|
HASURA_GRAPHQL_ADMIN_SECRET: youradminsecretkey
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
HASURA_GRAPHQL_JWT_SECRET: {"type":"RS256", "key": "<the-certificate-data-in-one-line>"}
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, (copy the certificate from above step or use generated config from https://hasura.io/jwt-config):
|
||||||
|
|
||||||
|
```
|
||||||
|
HASURA_GRAPHQL_JWT_SECRET: {"type":"RS256", "key": "-----BEGIN CERTIFICATE-----\nMIIDDTCCAfWgAwIBAgIJPhNlZ11IDrxbMA0GCSqGSIb3DQEBCQxIjAgNV\nBAMTGXRlc3QtaGdlLWp3dC5ldS5hdXRoMC5jb20wHhcNMTgwNzMwMTM1MjM1WhcN\nMzIwNDA3MTM1MjM1WjAkMSIwIAYDVQQDExl0ZXN0LWhnZS1qd3QuZXUuYXV0aDAu\nY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA13CivdSkNzRnOnR5iReDb+AgbL7BWjRiw3tRwjxRp5PYzvAGuj94y+R6LRh3QybYtsMFbSg5J7fNq6\nLd6yMpRMrUu8CBOnYY45D6b/2jlf+Vp8vEQuKvPMOOw8Ev6x7X3blcuXCELSwyL3\nAGHq9OpP2RV6V6CIE863IzzuYH5HDLzU35oMZqozgJVRJM0+6besH6TnSTNiA7xi\nBAqFaiQRNQRVi1CAUa0bLkN1XRp4AFy7d63VldO9sM+8QnCNHySdDr1XevVuq6DK\nLQyGexFFy4niALgHV0Q7QA+xP1c2G6rJomZmn4jl1avnlBpU87E58JMrRHOCj+5m\nXj22AQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBT6FvNkuUgu\YQ/i4lo5aOgwazAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEB\nADCLj+/L22pEKyqaIUlhHUJh7DAiDSLafy0fw56UCntzPhqiZVVRlhxeAKidkCLVIEbRLuxUoXiQSezPqMp//9xHegMp0f2VauVCFbg7EpUanYwvqFqjy9LWgH+SBz\n4uroLSYZ5g1EPsHtlArLRChA90caTX4e7Z7Xlu8vG2kHRJB5nC7ycdbMUvEWBMeI\ntn/pcb4mZ3/vlgj4UTEnCURe2UPmSJpxmPwXqBctvwdKHRMgFXhZxojWCi0z4ftf\nf8t8UJSIcbEblnkYe7wzRYy8tOXoMMHqGSisCdkWp/866029rJsKbwd8rVIyKNC5\nfrGYawv+0cxO6/Sir0meA=\n-----END CERTIFICATE-----"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Save changes.
|
||||||
|
|
||||||
|
## Configure the Auth0 Application
|
||||||
|
|
||||||
|
The project needs to be configured with your Auth0 domain and client ID in order for the authentication flow to work.
|
||||||
|
|
||||||
|
To do this, open `auth_config.json`, and replace the values within with your own Auth0 application credentials:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"domain": "<YOUR AUTH0 DOMAIN>",
|
||||||
|
"clientId": "<YOUR AUTH0 CLIENT ID>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create the initial tables
|
||||||
|
1. Add your database URL and admin secret in `hasura/config.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
endpoint: https://<hge-heroku-url>
|
||||||
|
admin_secret: <your-admin-secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run `hasura migrate apply` inside `hasura` directory to create the required tables and permissions for the app
|
||||||
|
|
||||||
|
## Create Auth0 Rule
|
||||||
|
|
||||||
|
Everytime user signups on Auth0, we need to sync that user into our postgres database. This is done using Auth0 rules. Create a Rule and insert the following code:
|
||||||
|
|
||||||
|
```
|
||||||
|
function (user, context, callback) {
|
||||||
|
const userId = user.user_id;
|
||||||
|
const nickname = user.nickname;
|
||||||
|
|
||||||
|
request.post({
|
||||||
|
headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': '<your-admin-secret>'},
|
||||||
|
url: 'http://myapp.herokuapp.com/v1alpha1/graphql',
|
||||||
|
body: `{\"query\":\"mutation($userId: String!, $nickname: String) {\\n insert_users(\\n objects: [{ auth0_id: $userId, name: $nickname }]\\n on_conflict: {\\n constraint: users_pkey\\n update_columns: [last_seen, name]\\n }\\n ) {\\n affected_rows\\n }\\n }\",\"variables\":{\"userId\":\"${userId}\",\"nickname\":\"${nickname}\"}}`
|
||||||
|
}, function(error, response, body){
|
||||||
|
console.log(body);
|
||||||
|
callback(null, user, context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run the application
|
||||||
|
|
||||||
|
`npm install && npm run serve`
|
||||||
|
|
||||||
|
> The app runs on port 3000 by default. You can change the port number, but you will also have to reconfigure the callback
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"domain": "",
|
||||||
|
"clientId": ""
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/app"]
|
||||||
|
};
|
2
community/sample-apps/vuejs-auth0-graphql/app/exec.ps1
Normal file
2
community/sample-apps/vuejs-auth0-graphql/app/exec.ps1
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
docker build --rm -t auth0-vue-01-login .
|
||||||
|
docker run -p 3000:3000 --pid=host auth0-vue-01-login
|
4
community/sample-apps/vuejs-auth0-graphql/app/exec.sh
Normal file
4
community/sample-apps/vuejs-auth0-graphql/app/exec.sh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
docker build --rm -t auth0-vue-01-login .
|
||||||
|
docker run -p 3000:3000 --pid=host auth0-vue-01-login
|
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><svg width='120px' height='120px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><defs><filter id="uil-ring-shadow" x="-100%" y="-100%" width="300%" height="300%"><feOffset result="offOut" in="SourceGraphic" dx="0" dy="0"></feOffset><feGaussianBlur result="blurOut" in="offOut" stdDeviation="0"></feGaussianBlur><feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend></filter></defs><path d="M10,50c0,0,0,0.5,0.1,1.4c0,0.5,0.1,1,0.2,1.7c0,0.3,0.1,0.7,0.1,1.1c0.1,0.4,0.1,0.8,0.2,1.2c0.2,0.8,0.3,1.8,0.5,2.8 c0.3,1,0.6,2.1,0.9,3.2c0.3,1.1,0.9,2.3,1.4,3.5c0.5,1.2,1.2,2.4,1.8,3.7c0.3,0.6,0.8,1.2,1.2,1.9c0.4,0.6,0.8,1.3,1.3,1.9 c1,1.2,1.9,2.6,3.1,3.7c2.2,2.5,5,4.7,7.9,6.7c3,2,6.5,3.4,10.1,4.6c3.6,1.1,7.5,1.5,11.2,1.6c4-0.1,7.7-0.6,11.3-1.6 c3.6-1.2,7-2.6,10-4.6c3-2,5.8-4.2,7.9-6.7c1.2-1.2,2.1-2.5,3.1-3.7c0.5-0.6,0.9-1.3,1.3-1.9c0.4-0.6,0.8-1.3,1.2-1.9 c0.6-1.3,1.3-2.5,1.8-3.7c0.5-1.2,1-2.4,1.4-3.5c0.3-1.1,0.6-2.2,0.9-3.2c0.2-1,0.4-1.9,0.5-2.8c0.1-0.4,0.1-0.8,0.2-1.2 c0-0.4,0.1-0.7,0.1-1.1c0.1-0.7,0.1-1.2,0.2-1.7C90,50.5,90,50,90,50s0,0.5,0,1.4c0,0.5,0,1,0,1.7c0,0.3,0,0.7,0,1.1 c0,0.4-0.1,0.8-0.1,1.2c-0.1,0.9-0.2,1.8-0.4,2.8c-0.2,1-0.5,2.1-0.7,3.3c-0.3,1.2-0.8,2.4-1.2,3.7c-0.2,0.7-0.5,1.3-0.8,1.9 c-0.3,0.7-0.6,1.3-0.9,2c-0.3,0.7-0.7,1.3-1.1,2c-0.4,0.7-0.7,1.4-1.2,2c-1,1.3-1.9,2.7-3.1,4c-2.2,2.7-5,5-8.1,7.1 c-0.8,0.5-1.6,1-2.4,1.5c-0.8,0.5-1.7,0.9-2.6,1.3L66,87.7l-1.4,0.5c-0.9,0.3-1.8,0.7-2.8,1c-3.8,1.1-7.9,1.7-11.8,1.8L47,90.8 c-1,0-2-0.2-3-0.3l-1.5-0.2l-0.7-0.1L41.1,90c-1-0.3-1.9-0.5-2.9-0.7c-0.9-0.3-1.9-0.7-2.8-1L34,87.7l-1.3-0.6 c-0.9-0.4-1.8-0.8-2.6-1.3c-0.8-0.5-1.6-1-2.4-1.5c-3.1-2.1-5.9-4.5-8.1-7.1c-1.2-1.2-2.1-2.7-3.1-4c-0.5-0.6-0.8-1.4-1.2-2 c-0.4-0.7-0.8-1.3-1.1-2c-0.3-0.7-0.6-1.3-0.9-2c-0.3-0.7-0.6-1.3-0.8-1.9c-0.4-1.3-0.9-2.5-1.2-3.7c-0.3-1.2-0.5-2.3-0.7-3.3 c-0.2-1-0.3-2-0.4-2.8c-0.1-0.4-0.1-0.8-0.1-1.2c0-0.4,0-0.7,0-1.1c0-0.7,0-1.2,0-1.7C10,50.5,10,50,10,50z" fill="#337ab7" filter="url(#uil-ring-shadow)"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" repeatCount="indefinite" dur="1s"></animateTransform></path></svg>
|
After Width: | Height: | Size: 2.2 KiB |
12024
community/sample-apps/vuejs-auth0-graphql/app/package-lock.json
generated
Normal file
12024
community/sample-apps/vuejs-auth0-graphql/app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
71
community/sample-apps/vuejs-auth0-graphql/app/package.json
Normal file
71
community/sample-apps/vuejs-auth0-graphql/app/package.json
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"name": "vuejs-auth0-graphql",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.16.4",
|
||||||
|
"morgan": "^1.9.1",
|
||||||
|
"vue-apollo": "^3.0.0-beta.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^1.2.14",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^5.7.1",
|
||||||
|
"@fortawesome/vue-fontawesome": "^0.1.5",
|
||||||
|
"@vue/cli-plugin-babel": "^3.4.0",
|
||||||
|
"@vue/cli-plugin-eslint": "^3.4.0",
|
||||||
|
"@vue/cli-service": "^3.4.0",
|
||||||
|
"@vue/eslint-config-prettier": "^3.0.5",
|
||||||
|
"auth0-js": "^9.10.0",
|
||||||
|
"babel-eslint": "^10.0.1",
|
||||||
|
"bootstrap": "^4.2.1",
|
||||||
|
"graphql-tag": "^2.9.0",
|
||||||
|
"highlight.js": "^9.14.2",
|
||||||
|
"jquery": "^3.3.1",
|
||||||
|
"node-sass": "^4.11.0",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"popper.js": "^1.14.7",
|
||||||
|
"samples-bootstrap-theme": "github:auth0-quickstarts/samples-bootstrap-theme",
|
||||||
|
"sass-loader": "^7.1.0",
|
||||||
|
"vue": "^2.6.2",
|
||||||
|
"vue-cli-plugin-apollo": "^0.19.2",
|
||||||
|
"vue-router": "^3.0.2",
|
||||||
|
"vue-template-compiler": "^2.6.2"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"vue"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"no-console": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allow": [
|
||||||
|
"error",
|
||||||
|
"warn"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "babel-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"singleQuote": false,
|
||||||
|
"semi": true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
};
|
BIN
community/sample-apps/vuejs-auth0-graphql/app/public/favicon.ico
Normal file
BIN
community/sample-apps/vuejs-auth0-graphql/app/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title>Vue.js Auth0 Authentication with Hasura GraphQL Engine</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
community/sample-apps/vuejs-auth0-graphql/app/public/logo.png
Normal file
BIN
community/sample-apps/vuejs-auth0-graphql/app/public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
14
community/sample-apps/vuejs-auth0-graphql/app/server.js
Normal file
14
community/sample-apps/vuejs-auth0-graphql/app/server.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const express = require("express");
|
||||||
|
const { join } = require("path");
|
||||||
|
const morgan = require("morgan");
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(morgan("dev"));
|
||||||
|
app.use(express.static(join(__dirname, "dist")));
|
||||||
|
|
||||||
|
app.use((_, res) => {
|
||||||
|
res.sendFile(join(__dirname, "dist", "index.html"));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3000, () => console.log("Listening on port 3000"));
|
30
community/sample-apps/vuejs-auth0-graphql/app/src/App.vue
Normal file
30
community/sample-apps/vuejs-auth0-graphql/app/src/App.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<nav-bar/>
|
||||||
|
<div class="container mt-5">
|
||||||
|
<router-view/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import "jquery";
|
||||||
|
import "samples-bootstrap-theme";
|
||||||
|
import "samples-bootstrap-theme/dist/css/auth0-theme.css";
|
||||||
|
|
||||||
|
import NavBar from "./components/NavBar";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
NavBar
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
try {
|
||||||
|
await this.$auth.renewTokens();
|
||||||
|
} catch {
|
||||||
|
// Supress the 'not logged in' error as we can illegitimately get that
|
||||||
|
// when processing the callback url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><svg width='120px' height='120px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><defs><filter id="uil-ring-shadow" x="-100%" y="-100%" width="300%" height="300%"><feOffset result="offOut" in="SourceGraphic" dx="0" dy="0"></feOffset><feGaussianBlur result="blurOut" in="offOut" stdDeviation="0"></feGaussianBlur><feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend></filter></defs><path d="M10,50c0,0,0,0.5,0.1,1.4c0,0.5,0.1,1,0.2,1.7c0,0.3,0.1,0.7,0.1,1.1c0.1,0.4,0.1,0.8,0.2,1.2c0.2,0.8,0.3,1.8,0.5,2.8 c0.3,1,0.6,2.1,0.9,3.2c0.3,1.1,0.9,2.3,1.4,3.5c0.5,1.2,1.2,2.4,1.8,3.7c0.3,0.6,0.8,1.2,1.2,1.9c0.4,0.6,0.8,1.3,1.3,1.9 c1,1.2,1.9,2.6,3.1,3.7c2.2,2.5,5,4.7,7.9,6.7c3,2,6.5,3.4,10.1,4.6c3.6,1.1,7.5,1.5,11.2,1.6c4-0.1,7.7-0.6,11.3-1.6 c3.6-1.2,7-2.6,10-4.6c3-2,5.8-4.2,7.9-6.7c1.2-1.2,2.1-2.5,3.1-3.7c0.5-0.6,0.9-1.3,1.3-1.9c0.4-0.6,0.8-1.3,1.2-1.9 c0.6-1.3,1.3-2.5,1.8-3.7c0.5-1.2,1-2.4,1.4-3.5c0.3-1.1,0.6-2.2,0.9-3.2c0.2-1,0.4-1.9,0.5-2.8c0.1-0.4,0.1-0.8,0.2-1.2 c0-0.4,0.1-0.7,0.1-1.1c0.1-0.7,0.1-1.2,0.2-1.7C90,50.5,90,50,90,50s0,0.5,0,1.4c0,0.5,0,1,0,1.7c0,0.3,0,0.7,0,1.1 c0,0.4-0.1,0.8-0.1,1.2c-0.1,0.9-0.2,1.8-0.4,2.8c-0.2,1-0.5,2.1-0.7,3.3c-0.3,1.2-0.8,2.4-1.2,3.7c-0.2,0.7-0.5,1.3-0.8,1.9 c-0.3,0.7-0.6,1.3-0.9,2c-0.3,0.7-0.7,1.3-1.1,2c-0.4,0.7-0.7,1.4-1.2,2c-1,1.3-1.9,2.7-3.1,4c-2.2,2.7-5,5-8.1,7.1 c-0.8,0.5-1.6,1-2.4,1.5c-0.8,0.5-1.7,0.9-2.6,1.3L66,87.7l-1.4,0.5c-0.9,0.3-1.8,0.7-2.8,1c-3.8,1.1-7.9,1.7-11.8,1.8L47,90.8 c-1,0-2-0.2-3-0.3l-1.5-0.2l-0.7-0.1L41.1,90c-1-0.3-1.9-0.5-2.9-0.7c-0.9-0.3-1.9-0.7-2.8-1L34,87.7l-1.3-0.6 c-0.9-0.4-1.8-0.8-2.6-1.3c-0.8-0.5-1.6-1-2.4-1.5c-3.1-2.1-5.9-4.5-8.1-7.1c-1.2-1.2-2.1-2.7-3.1-4c-0.5-0.6-0.8-1.4-1.2-2 c-0.4-0.7-0.8-1.3-1.1-2c-0.3-0.7-0.6-1.3-0.9-2c-0.3-0.7-0.6-1.3-0.8-1.9c-0.4-1.3-0.9-2.5-1.2-3.7c-0.3-1.2-0.5-2.3-0.7-3.3 c-0.2-1-0.3-2-0.4-2.8c-0.1-0.4-0.1-0.8-0.1-1.2c0-0.4,0-0.7,0-1.1c0-0.7,0-1.2,0-1.7C10,50.5,10,50,10,50z" fill="#337ab7" filter="url(#uil-ring-shadow)"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" repeatCount="indefinite" dur="1s"></animateTransform></path></svg>
|
After Width: | Height: | Size: 2.2 KiB |
@ -0,0 +1,128 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
import auth0 from "auth0-js";
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import authConfig from "../../auth_config.json";
|
||||||
|
|
||||||
|
const webAuth = new auth0.WebAuth({
|
||||||
|
domain: authConfig.domain,
|
||||||
|
redirectUri: `${window.location.origin}/callback`,
|
||||||
|
clientID: authConfig.clientId,
|
||||||
|
responseType: "token id_token",
|
||||||
|
scope: "openid profile"
|
||||||
|
});
|
||||||
|
|
||||||
|
const localStorageKey = "loggedIn";
|
||||||
|
const loginEvent = "loginEvent";
|
||||||
|
|
||||||
|
class AuthService extends EventEmitter {
|
||||||
|
idToken = null;
|
||||||
|
profile = null;
|
||||||
|
tokenExpiry = null;
|
||||||
|
|
||||||
|
login(customState) {
|
||||||
|
webAuth.authorize();
|
||||||
|
}
|
||||||
|
|
||||||
|
logOut() {
|
||||||
|
localStorage.removeItem(localStorageKey);
|
||||||
|
|
||||||
|
this.idToken = null;
|
||||||
|
this.tokenExpiry = null;
|
||||||
|
this.profile = null;
|
||||||
|
|
||||||
|
webAuth.logout({
|
||||||
|
returnTo: `${window.location.origin}`
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit(loginEvent, { loggedIn: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAuthentication() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
webAuth.parseHash((err, authResult) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
this.localLogin(authResult);
|
||||||
|
resolve(authResult.idToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isAuthenticated() {
|
||||||
|
return (
|
||||||
|
Date.now() < this.tokenExpiry &&
|
||||||
|
localStorage.getItem(localStorageKey) === "true"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isIdTokenValid() {
|
||||||
|
return (
|
||||||
|
this.idToken &&
|
||||||
|
this.tokenExpiry &&
|
||||||
|
Date.now() < this.tokenExpiry
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getIdToken() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (this.isIdTokenValid()) {
|
||||||
|
resolve(this.idToken);
|
||||||
|
} else if (this.isAuthenticated()) {
|
||||||
|
this.renewTokens().then(authResult => {
|
||||||
|
resolve(authResult.idToken);
|
||||||
|
}, reject);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
localLogin(authResult) {
|
||||||
|
console.log(authResult);
|
||||||
|
this.idToken = authResult.idToken;
|
||||||
|
this.profile = authResult.idTokenPayload;
|
||||||
|
|
||||||
|
// Convert the expiry time from seconds to milliseconds,
|
||||||
|
// required by the Date constructor
|
||||||
|
this.tokenExpiry = new Date(this.profile.exp * 1000);
|
||||||
|
|
||||||
|
localStorage.setItem(localStorageKey, "true");
|
||||||
|
localStorage.setItem("apollo-token", authResult.idToken);
|
||||||
|
|
||||||
|
this.emit(loginEvent, {
|
||||||
|
loggedIn: true,
|
||||||
|
profile: authResult.idTokenPayload,
|
||||||
|
state: authResult.appState || {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renewTokens() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log(localStorage.getItem(localStorageKey));
|
||||||
|
if (localStorage.getItem(localStorageKey) !== "true") {
|
||||||
|
return reject("Not logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
webAuth.checkSession({}, (err, authResult) => {
|
||||||
|
if (err) {
|
||||||
|
console.log('inside');
|
||||||
|
console.log(err);
|
||||||
|
localStorage.setItem(localStorageKey, "false");
|
||||||
|
localStorage.removeItem("apollo-token");
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
this.localLogin(authResult);
|
||||||
|
resolve(authResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = new AuthService();
|
||||||
|
|
||||||
|
service.setMaxListeners(5);
|
||||||
|
|
||||||
|
export default service;
|
@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<div class="spinner">
|
||||||
|
<img src="../assets/loading.svg" alt="Loading">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
handleLoginEvent(data) {
|
||||||
|
// this.$router.push(data.state.target || "/");
|
||||||
|
window.location.href = data.state.target || "/";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$auth.handleAuthentication();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.spinner {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: white;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<div class="nav-container">
|
||||||
|
<nav class="navbar navbar-expand-md navbar-light bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand"></div>
|
||||||
|
<button
|
||||||
|
class="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav mr-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link to="/" class="nav-link">Home</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="navbar-nav d-none d-md-block">
|
||||||
|
<li v-if="!isAuthenticated" class="nav-item">
|
||||||
|
<button
|
||||||
|
id="qsLoginBtn"
|
||||||
|
class="btn btn-primary btn-margin"
|
||||||
|
@click.prevent="login"
|
||||||
|
>Login</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item dropdown" v-if="isAuthenticated">
|
||||||
|
<a
|
||||||
|
class="nav-link dropdown-toggle"
|
||||||
|
href="#"
|
||||||
|
id="profileDropDown"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
>
|
||||||
|
<img :src="profile.picture" alt="User's profile picture" class="nav-user-profile">
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<div class="dropdown-header">{{ profile.name }}</div>
|
||||||
|
<router-link to="/profile" class="dropdown-item dropdown-profile">
|
||||||
|
<span class="icon icon-profile"></span> Profile
|
||||||
|
</router-link>
|
||||||
|
<a id="qsLogoutBtn" href="#" class="dropdown-item" @click.prevent="logout">
|
||||||
|
<span class="icon icon-power"></span> Log out
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="navbar-nav d-md-none" v-if="!isAuthenticated">
|
||||||
|
<button class="btn btn-primary btn-block" @click="login">Log in</button>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="navbar-nav d-md-none" v-if="isAuthenticated">
|
||||||
|
<li class="nav-item">
|
||||||
|
<span class="user-info">
|
||||||
|
<img :src="profile.picture" alt="User's profile picture" class="nav-user-profile d-inline-block">
|
||||||
|
<h6 class="d-inline-block">{{ profile.name }}</h6>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="icon icon-profile"></span>
|
||||||
|
<router-link to="/profile">Profile</router-link>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<span class="icon icon-power"></span>
|
||||||
|
<a id="qsLogoutBtn" href="#" class @click.prevent="logout">Log out</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "NavBar",
|
||||||
|
beforeCreate() {
|
||||||
|
this.$auth.renewTokens();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
login() {
|
||||||
|
this.$auth.login();
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
this.$auth.logOut();
|
||||||
|
this.$router.push({ path: "/" });
|
||||||
|
},
|
||||||
|
handleLoginEvent(data) {
|
||||||
|
this.isAuthenticated = data.loggedIn;
|
||||||
|
this.profile = data.profile;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isAuthenticated: false,
|
||||||
|
profile: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -0,0 +1,31 @@
|
|||||||
|
import hljs from "highlight.js/lib/highlight";
|
||||||
|
import json from "highlight.js/lib/languages/json";
|
||||||
|
import "highlight.js/styles/monokai-sublime.css";
|
||||||
|
|
||||||
|
hljs.registerLanguage("json", json);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
deep: true,
|
||||||
|
bind: function(el, binding) {
|
||||||
|
// on first bind, highlight all targets
|
||||||
|
let targets = el.querySelectorAll("code");
|
||||||
|
targets.forEach(target => {
|
||||||
|
// if a value is directly assigned to the directive, use this
|
||||||
|
// instead of the element content.
|
||||||
|
if (binding.value) {
|
||||||
|
target.textContent = binding.value;
|
||||||
|
}
|
||||||
|
hljs.highlightBlock(target);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
componentUpdated: function(el, binding) {
|
||||||
|
// after an update, re-fill the content and then highlight
|
||||||
|
let targets = el.querySelectorAll("code");
|
||||||
|
targets.forEach(target => {
|
||||||
|
if (binding.value) {
|
||||||
|
target.textContent = binding.value;
|
||||||
|
hljs.highlightBlock(target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
25
community/sample-apps/vuejs-auth0-graphql/app/src/main.js
Normal file
25
community/sample-apps/vuejs-auth0-graphql/app/src/main.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Vue from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
import AuthPlugin from "./plugins/auth";
|
||||||
|
import HighlightJs from "./directives/highlight";
|
||||||
|
|
||||||
|
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||||
|
import { faLink } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
|
import { createProvider } from './vue-apollo'
|
||||||
|
|
||||||
|
Vue.use(AuthPlugin);
|
||||||
|
Vue.directive("highlightjs", HighlightJs);
|
||||||
|
|
||||||
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
library.add(faLink);
|
||||||
|
|
||||||
|
Vue.component("font-awesome-icon", FontAwesomeIcon);
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
router,
|
||||||
|
apolloProvider: createProvider(),
|
||||||
|
render: h => h(App)
|
||||||
|
}).$mount("#app");
|
@ -0,0 +1,21 @@
|
|||||||
|
import authService from "../auth/authService";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install(Vue) {
|
||||||
|
Vue.prototype.$auth = authService;
|
||||||
|
|
||||||
|
Vue.mixin({
|
||||||
|
created() {
|
||||||
|
if (this.handleLoginEvent) {
|
||||||
|
authService.addListener("loginEvent", this.handleLoginEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyed() {
|
||||||
|
if (this.handleLoginEvent) {
|
||||||
|
authService.removeListener("loginEvent", this.handleLoginEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
40
community/sample-apps/vuejs-auth0-graphql/app/src/router.js
Normal file
40
community/sample-apps/vuejs-auth0-graphql/app/src/router.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import Vue from "vue";
|
||||||
|
import Router from "vue-router";
|
||||||
|
import Home from "./views/Home.vue";
|
||||||
|
import Profile from "./views/Profile.vue";
|
||||||
|
import Callback from "./components/Callback.vue";
|
||||||
|
import auth from "./auth/authService";
|
||||||
|
|
||||||
|
Vue.use(Router);
|
||||||
|
|
||||||
|
const router = new Router({
|
||||||
|
mode: "history",
|
||||||
|
base: process.env.BASE_URL,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "home",
|
||||||
|
component: Home
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/profile",
|
||||||
|
name: "profile",
|
||||||
|
component: Profile
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/callback",
|
||||||
|
name: "callback",
|
||||||
|
component: Callback
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (to.path === "/" || to.path === "/callback" || auth.isAuthenticated()) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.login({ target: to.path });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="spinner" v-if="isLoading">
|
||||||
|
<img src="../assets/loading.svg" alt="Loading">
|
||||||
|
</div>
|
||||||
|
<div class="text-center hero" v-if="!isLoading">
|
||||||
|
<img class="mb-3 app-logo" src="/logo.png" alt="Vue.js logo">
|
||||||
|
<div v-if="!isAuthenticated">
|
||||||
|
<h1 class="mb-4">
|
||||||
|
Login to view articles
|
||||||
|
</h1>
|
||||||
|
<p class="lead">
|
||||||
|
This is a sample application that demonstrates an authentication flow for an SPA, using
|
||||||
|
<a
|
||||||
|
href="https://vuejs.org"
|
||||||
|
>Vue.js</a> and making a authenticated GraphQL query to <a href="https://github.com/hasura/graphql-engine">Hasura GraphQL Engine</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="isAuthenticated">
|
||||||
|
<h1 class="mb-4">
|
||||||
|
Articles written by me
|
||||||
|
</h1>
|
||||||
|
<div v-for="a in article" :key="a.id">
|
||||||
|
{{a.id}}. {{ a.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
export default {
|
||||||
|
name: "home",
|
||||||
|
methods: {
|
||||||
|
handleLoginEvent(data) {
|
||||||
|
this.isAuthenticated = data.loggedIn;
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeCreate() {
|
||||||
|
this.isLoading = true;
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isAuthenticated: false,
|
||||||
|
isLoading: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
// Simple query that will update the 'article' vue property
|
||||||
|
article: gql`query {
|
||||||
|
article {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.next-steps {
|
||||||
|
.fa-link {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.spinner {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: white;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row align-items-center profile-header">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<img :src="profile.picture" alt="User's profile picture" class="rounded-circle img-fluid profile-picture">
|
||||||
|
</div>
|
||||||
|
<div class="col-md">
|
||||||
|
<h2>{{ profile.name }}</h2>
|
||||||
|
<p class="lead text-muted">{{ profile.email }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<pre v-highlightjs class="rounded"><code class="json">{{ JSON.stringify(profile, null, 2) }}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
profile: this.$auth.profile
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleLoginEvent(data) {
|
||||||
|
this.profile = data.profile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
117
community/sample-apps/vuejs-auth0-graphql/app/src/vue-apollo.js
Normal file
117
community/sample-apps/vuejs-auth0-graphql/app/src/vue-apollo.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
import Vue from 'vue'
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client'
|
||||||
|
import authService from './auth/authService'
|
||||||
|
|
||||||
|
// Install the vue plugin
|
||||||
|
Vue.use(VueApollo)
|
||||||
|
|
||||||
|
// Name of the localStorage item
|
||||||
|
const AUTH_TOKEN = 'apollo-token'
|
||||||
|
|
||||||
|
// Http endpoint
|
||||||
|
const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:8080/v1alpha1/graphql'
|
||||||
|
// Files URL root
|
||||||
|
export const filesRoot = process.env.VUE_APP_FILES_ROOT || httpEndpoint.substr(0, httpEndpoint.indexOf('/graphql'))
|
||||||
|
|
||||||
|
Vue.prototype.$filesRoot = filesRoot
|
||||||
|
|
||||||
|
// Config
|
||||||
|
const defaultOptions = {
|
||||||
|
// You can use `https` for secure connection (recommended in production)
|
||||||
|
httpEndpoint,
|
||||||
|
// You can use `wss` for secure connection (recommended in production)
|
||||||
|
// Use `null` to disable subscriptions
|
||||||
|
wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:8080/v1alpha1/graphql',
|
||||||
|
// LocalStorage token
|
||||||
|
tokenName: AUTH_TOKEN,
|
||||||
|
// Enable Automatic Query persisting with Apollo Engine
|
||||||
|
persisting: false,
|
||||||
|
// Use websockets for everything (no HTTP)
|
||||||
|
// You need to pass a `wsEndpoint` for this to work
|
||||||
|
websocketsOnly: false,
|
||||||
|
// Is being rendered on the server?
|
||||||
|
ssr: false,
|
||||||
|
|
||||||
|
// Override default apollo link
|
||||||
|
// note: don't override httpLink here, specify httpLink options in the
|
||||||
|
// httpLinkOptions property of defaultOptions.
|
||||||
|
// link: myLink
|
||||||
|
|
||||||
|
// Override default cache
|
||||||
|
// cache: myCache
|
||||||
|
|
||||||
|
// Override the way the Authorization header is set
|
||||||
|
// getAuth: (tokenName) => ...
|
||||||
|
|
||||||
|
// Additional ApolloClient options
|
||||||
|
// apollo: { ... }
|
||||||
|
|
||||||
|
// Client local data (see apollo-link-state)
|
||||||
|
// clientState: { resolvers: { ... }, defaults: { ... } }
|
||||||
|
getAuth: tokenName => {
|
||||||
|
// get the authentication token from local storage if it exists
|
||||||
|
// return the headers to the context so httpLink can read them
|
||||||
|
const token = localStorage.getItem('apollo-token')
|
||||||
|
if (token) {
|
||||||
|
return 'Bearer ' + token
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this in the Vue app file
|
||||||
|
export function createProvider (options = {}) {
|
||||||
|
// Create apollo client
|
||||||
|
const { apolloClient, wsClient } = createApolloClient({
|
||||||
|
...defaultOptions,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
apolloClient.wsClient = wsClient
|
||||||
|
|
||||||
|
// Create vue apollo provider
|
||||||
|
const apolloProvider = new VueApollo({
|
||||||
|
defaultClient: apolloClient,
|
||||||
|
defaultOptions: {
|
||||||
|
$query: {
|
||||||
|
// fetchPolicy: 'cache-and-network',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errorHandler (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return apolloProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually call this when user log in
|
||||||
|
export async function onLogin (apolloClient, token) {
|
||||||
|
if (typeof localStorage !== 'undefined' && token) {
|
||||||
|
localStorage.setItem(AUTH_TOKEN, token)
|
||||||
|
}
|
||||||
|
if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
|
||||||
|
try {
|
||||||
|
await apolloClient.resetStore()
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('%cError on cache reset (login)', 'color: orange;', e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually call this when user log out
|
||||||
|
export async function onLogout (apolloClient) {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
localStorage.removeItem(AUTH_TOKEN)
|
||||||
|
}
|
||||||
|
if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
|
||||||
|
try {
|
||||||
|
await apolloClient.resetStore()
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('%cError on cache reset (logout)', 'color: orange;', e.message)
|
||||||
|
}
|
||||||
|
}
|
17
community/sample-apps/vuejs-auth0-graphql/app/vue.config.js
Normal file
17
community/sample-apps/vuejs-auth0-graphql/app/vue.config.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const webpack = require("webpack");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
devServer: {
|
||||||
|
port: 3000
|
||||||
|
},
|
||||||
|
configureWebpack: {
|
||||||
|
plugins: [
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
$: "jquery",
|
||||||
|
jquery: "jquery",
|
||||||
|
"window.jQuery": "jquery",
|
||||||
|
jQuery: "jquery"
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
endpoint: http://localhost:8070
|
@ -0,0 +1,3 @@
|
|||||||
|
- args:
|
||||||
|
sql: DROP TABLE "public"."users"
|
||||||
|
type: run_sql
|
@ -0,0 +1,8 @@
|
|||||||
|
- args:
|
||||||
|
sql: CREATE TABLE "public"."users"("auth0_id" text NOT NULL, "name" text NOT NULL,
|
||||||
|
"created_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("auth0_id") );
|
||||||
|
type: run_sql
|
||||||
|
- args:
|
||||||
|
name: users
|
||||||
|
schema: public
|
||||||
|
type: add_existing_table_or_view
|
@ -0,0 +1,3 @@
|
|||||||
|
- args:
|
||||||
|
sql: DROP TABLE "public"."article"
|
||||||
|
type: run_sql
|
@ -0,0 +1,8 @@
|
|||||||
|
- args:
|
||||||
|
sql: CREATE TABLE "public"."article"("id" serial NOT NULL, "title" text NOT NULL,
|
||||||
|
"user_id" text NOT NULL, PRIMARY KEY ("id") );
|
||||||
|
type: run_sql
|
||||||
|
- args:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
type: add_existing_table_or_view
|
@ -0,0 +1,6 @@
|
|||||||
|
- args:
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: users
|
||||||
|
schema: public
|
||||||
|
type: drop_select_permission
|
@ -0,0 +1,16 @@
|
|||||||
|
- args:
|
||||||
|
permission:
|
||||||
|
allow_aggregations: false
|
||||||
|
columns:
|
||||||
|
- auth0_id
|
||||||
|
- name
|
||||||
|
- created_at
|
||||||
|
filter:
|
||||||
|
auth0_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
limit: null
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: users
|
||||||
|
schema: public
|
||||||
|
type: create_select_permission
|
@ -0,0 +1,6 @@
|
|||||||
|
- args:
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
type: drop_select_permission
|
@ -0,0 +1,16 @@
|
|||||||
|
- args:
|
||||||
|
permission:
|
||||||
|
allow_aggregations: false
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- user_id
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
limit: null
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
type: create_select_permission
|
@ -0,0 +1,6 @@
|
|||||||
|
- args:
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
type: drop_insert_permission
|
@ -0,0 +1,16 @@
|
|||||||
|
- args:
|
||||||
|
permission:
|
||||||
|
allow_upsert: true
|
||||||
|
check:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- user_id
|
||||||
|
set: {}
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
type: create_insert_permission
|
@ -0,0 +1,6 @@
|
|||||||
|
- args:
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
type: drop_update_permission
|
@ -0,0 +1,15 @@
|
|||||||
|
- args:
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- user_id
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
set: {}
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
type: create_update_permission
|
@ -0,0 +1,6 @@
|
|||||||
|
- args:
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
type: drop_delete_permission
|
@ -0,0 +1,10 @@
|
|||||||
|
- args:
|
||||||
|
permission:
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
type: create_delete_permission
|
@ -0,0 +1,3 @@
|
|||||||
|
- args:
|
||||||
|
sql: ALTER TABLE "public"."article" DROP CONSTRAINT "article_user_id_fkey"
|
||||||
|
type: run_sql
|
@ -0,0 +1,4 @@
|
|||||||
|
- args:
|
||||||
|
sql: ALTER TABLE "public"."article" ADD FOREIGN KEY ("user_id") REFERENCES "public"."users"
|
||||||
|
("auth0_id")
|
||||||
|
type: run_sql
|
Loading…
Reference in New Issue
Block a user