Related
I have a custom subdomain connected to Firebase Hosting - api.example.com
Additionally I have a function in Firebase Functions
exports.test = functions
.region('europe-west3')
.https.onRequest((request, response) => {
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const origin = request.headers.origin;
const options = { maxAge: expiresIn, httpOnly: true, secure: true, sameSite: 'strict', };
response.cookie('__session', "123456", options);
response.set('Access-Control-Allow-Origin', origin);
response.set('Access-Control-Allow-Credentials', true);
response.end(JSON.stringify({ status: 'success' }));
})
my Firebase.json looks like the following
{
"functions": {
"source": "functions"
},
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [{
"source": "/test",
"function": "test"
}]
}
}
Now when I'm trying to make a POST request to https://api.example.com/test
I get a error
Your client does not have permission to get URL /test/test from this server.
which makes sense as the URL says /test/test/
What am I doing wrong here? Any suggestions? Thanks.
Firebase Hosting does not support regions other than us-central1 for Cloud Functions. See the documentation:
Important: Firebase Hosting supports Cloud Functions in us-central1 only.
See also this issue on GitHub: https://github.com/firebase/firebase-tools/issues/842
The repo with this code can be found HERE
I’m working on sourcing and combining data from Shopify Storefront and GraphQL Admin APIs into Gatsby. For sourcing Storefront API I use gatsby-source-shopify, though I wasn’t able to find source plugin for Shopify Admin API and ended up using gatsby-source-graphql.
I have created a private Shopify app and granted all permissions and was able to fetch data using curl example from Shopify GraphQL Admin API documentation.
curl -X POST \
"https://shop-name.myshopify.com/admin/api/2019-04/graphql.json" \
-H "Content-Type: application/graphql" \
-H "X-Shopify-Access-Token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d '
{
shop {
name
}
}
'
Output:
{"data":{"shop":{"name":"Shop Name"}},"extensions":{"cost":{"requestedQueryCost":1,"actualQueryCost":1,"throttleStatus":{"maximumAvailable":1000.0,"currentlyAvailable":999,"restoreRate":50.0}}}}
Error message:
success open and validate gatsby-configs — 0.007 s
success load plugins — 0.345 s
success onPreInit — 0.005 s
success initialize cache — 0.015 s
success copy gatsby files — 0.068 s
success onPreBootstrap — 0.006 s
error Plugin gatsby-source-graphql returned an error
Error: Parse error on "operationName" (STRING) at [1, 2]
gatsby-source-shopify/sandbox starting to fetch data from Shopify
gatsby-source-shopify/sandbox fetched and processed productTypes: 287.590ms
gatsby-source-shopify/sandbox fetched and processed policies: 297.440ms
gatsby-source-shopify/sandbox fetched and processed blogs: 302.418ms
gatsby-source-shopify/sandbox fetched and processed articles: 344.917ms
gatsby-source-shopify/sandbox fetched and processed collections: 8603.841ms
gatsby-source-shopify/sandbox fetched and processed products: 20362.561ms
gatsby-source-shopify/sandbox finished fetching data from Shopify: 20369.525ms
warning The gatsby-source-graphql plugin has generated no Gatsby nodes. Do you need it?
success source and transform nodes — 21.169 s
warning Multiple node fields resolve to the same GraphQL field `ShopifyArticle.blog` - [`blog`, `blog___NODE`]. Gatsby will use `blog___NODE`.
warning Multiple node fields resolve to the same GraphQL field `ShopifyCollection.products` - [`products`, `products___NODE`]. Gatsby will use `products___NODE`.
warning Multiple node fields resolve to the same GraphQL field `ShopifyProduct.options` - [`options`, `options___NODE`]. Gatsby will use `options___NODE`.
warning Multiple node fields resolve to the same GraphQL field `ShopifyProduct.variants` - [`variants`, `variants___NODE`]. Gatsby will use `variants___NODE`.
success building schema — 0.283 s
success createPages — 0.000 s
success createPagesStatefully — 0.042 s
success onPreExtractQueries — 0.005 s
success update schema — 0.019 s
success extract queries from components — 0.098 s
success run static queries — 0.021 s — 2/2 96.96 queries/second
success run page queries — 0.014 s — 4/4 301.32 queries/second
success write out page data — 0.012 s
success write out redirect data — 0.001 s
success Build manifest and related icons — 0.104 s
success onPostBootstrap — 0.106 s
info bootstrap finished - 23.946 s
WARNING Compiled with 1 warnings 6:04:43 PM
Module Warning (from ./node_modules/eslint-loader/index.js):
/Users/skok/dev/gatsby-shopify-starter/src/components/collections.js
2:10 warning 'useStaticQuery' is defined but never used no-unused-vars
2:26 warning 'graphql' is defined but never used no-unused-vars
✖ 2 problems (0 errors, 2 warnings)
You may use special comments to disable some warnings.
Use // eslint-disable-next-line to ignore the next line.
Use /* eslint-disable */ to ignore all warnings in a file.
You can now view gatsby-shopify in the browser.
http://localhost:8000/
View GraphiQL, an in-browser IDE, to explore your site's data and schema
http://localhost:8000/___graphql
Note that the development build is not optimized.
To create a production build, use npm run build
warning ⚠ 「wdm」:
info ℹ 「wdm」: Compiled with warnings.
WAIT Compiling... 6:04:43 PM
info ℹ 「wdm」: Compiling...
WARNING Compiled with 1 warnings 6:04:43 PM
Module Warning (from ./node_modules/eslint-loader/index.js):
/Users/skok/dev/gatsby-shopify-starter/src/components/collections.js
2:10 warning 'useStaticQuery' is defined but never used no-unused-vars
2:26 warning 'graphql' is defined but never used no-unused-vars
✖ 2 problems (0 errors, 2 warnings)
You may use special comments to disable some warnings.
Use // eslint-disable-next-line to ignore the next line.
Use /* eslint-disable */ to ignore all warnings in a file.
warning ⚠ 「wdm」:
info ℹ 「wdm」: Compiled with warnings.
gatsby-config.js:
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`
});
module.exports = {
siteMetadata: {
title: `Gatsby Shopify`,
description: `Gatsby Shopify Starter.`,
author: `#iamskok`,
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: "gatsby-source-graphql",
options: {
// This type will contain remote schema Query type
typeName: "ShopifyAdmin",
// This is field under which it's accessible
fieldName: "admin",
// Url to query from
url: `https://${process.env.SHOP_NAME}.myshopify.com/admin/api/2019-04/graphql.json`,
headers: {
// Learn about environment variables: https://gatsby.dev/env-vars
'X-Shopify-Access-Token': `${process.env.ADMIN_PASSWORD}`,
'Content-Type': 'application/graphql'
},
fetchOptions: {
method: 'POST'
},
refetchInterval: 60
},
},
{
resolve: `gatsby-source-shopify`,
options: {
// The domain name of your Shopify shop. This is required.
// Example: 'gatsby-source-shopify-test-shop' if your Shopify address is
// 'gatsby-source-shopify-test-shop.myshopify.com'.
shopName: process.env.SHOP_NAME,
// An API access token to your Shopify shop. This is required.
// You can generate an access token in the "Manage private apps" section
// of your shop's Apps settings. In the Storefront API section, be sure
// to select "Allow this app to access your storefront data using the
// Storefront API".
// See: https://help.shopify.com/api/custom-storefronts/storefront-api/getting-started#authentication
accessToken: process.env.STOREFRONT_ACCESS_TOKEN,
// Set verbose to true to display a verbose output on `npm run develop`
// or `npm run build`. This prints which nodes are being fetched and how
// much time was required to fetch and process the data.
// Defaults to true.
verbose: true,
},
},
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
],
}
./src/collections.js:
import React from 'react'
import { useStaticQuery, graphql } from 'gatsby'
const Collections = () => {
const storefrontData = useStaticQuery(
graphql`
query {
allShopifyCollection {
edges {
node {
id
description
descriptionHtml
handle
image {
altText
id
src
localFile {
childImageSharp {
fluid(maxWidth: 910) {
...GatsbyImageSharpFluid_withWebp_tracedSVG
}
}
}
}
products {
id
handle
title
}
title
updatedAt
}
}
}
}
`
)
const adminData = useStaticQuery(
graphql`
query {
admin {
shop {
name
}
}
}
`
)
console.log('storefront data:', JSON.stringify(storefrontData));
console.log('admin data:', JSON.stringify(adminData));
return (
<div className="collections">
<h2>Collections</h2>
</div>
)
}
export default Collections
index.js:
import React from 'react'
import Layout from '../components/layout'
import Collections from '../components/collections'
const IndexPage = () => (
<Layout>
<h1>Shopify</h1>
<Collections />
</Layout>
)
export default IndexPage
package.json:
{
"name": "gatsby-shopify",
"private": true,
"description": "Gatsby Shopify Starter",
"version": "0.1.0",
"author": "Vladimir Skok <skok#vova.io>",
"dependencies": {
"gatsby": "^2.4.2",
"gatsby-image": "^2.0.41",
"gatsby-plugin-manifest": "^2.1.1",
"gatsby-plugin-offline": "^2.1.0",
"gatsby-plugin-react-helmet": "^3.0.12",
"gatsby-plugin-sharp": "^2.0.36",
"gatsby-source-filesystem": "^2.0.33",
"gatsby-source-graphql": "^2.0.18",
"gatsby-source-shopify": "^2.0.28",
"gatsby-transformer-sharp": "^2.1.19",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-helmet": "^5.2.1"
},
"devDependencies": {
"prettier": "^1.17.0"
},
"keywords": [
"gatsby"
],
"license": "MIT",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --write src/**/*.{js,jsx}",
"start": "npm run develop",
"serve": "gatsby serve",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\""
},
"repository": {
"type": "git",
"url": "https://github.com/iamskok/gatsby-shopify-starter"
},
"bugs": {
"url": "https://github.com/iamskok/gatsby-shopify-starter/issues"
}
}
The expected result is to make a successful GraphQL query to Shopify Admin API. The actual result is server error with 400 status code.
Your gatsby-source-graphql setting is different than the curl command in the following seting:
the POST HTTP Method
Content-Type: application/graphql header
In order to match the curl command, you could try the setting below:
{
resolve: "gatsby-source-graphql",
options: {
typeName: "ShopifyAdmin",
fieldName: "admin",
url: `https://${process.env.SHOP_NAME}.myshopify.com/admin/api/2019-04/orders.json`,
headers: {
'X-Shopify-Access-Token': `${process.env.ADMIN_PASSWORD}`,
+ 'Content-Type': 'application/graphql',
},
+ fetchOptions: {
+ method: 'POST'
+ }
refetchInterval: 60
},
},
You have a few issues here.
First you need to pass X-Shopify-Storefront-Access-Token not X-Shopify-Access-Token. (
if you are really using a private app )
Then you need to request /api/2019-04/graphql not /admin/api/2019-04/graphql.json".
Please refer to this doc: https://help.shopify.com/en/api/custom-storefronts/storefront-api/getting-started#accessing-the-storefront-api-graphql-endpoint
I am following these instructions to create a basic web scraper that executes in Lambda. I have experience writing selenium code, but not with Node JS. I got the project running in Lambda, but when I tried editing the project locally in order to execute the selenium code I want, It doesn't work. Anything in the exports.handler doesn't get executed when I run node index.js. How would I execute this project locally? Thanks!
This is what I did:
index.js
exports.handler = async (event) => {
console.log('hello world');
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!')
};
return response;
};
package.json
"scripts": {
"locally": "node -e \"console.log(require('./index').handler(require('./event.json')));\""
}
event.json
{
"Records": [
{
"eventVersion": "2.0",
"eventSource": "aws:s3",
"awsRegion": "eu-central-1",
"eventTime": "1970-01-01T00:00:00.000Z",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "AIDAJDPLRKLG7UEXAMPLE"
},
"requestParameters": {
"sourceIPAddress": "127.0.0.1"
},
"responseElements": {
"x-amz-request-id": "C3D13FE58DE4C810",
"x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"
},
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "testConfigRule",
"bucket": {
"name": "my-bucket",
"ownerIdentity": {
"principalId": "A3NL1KOZZKExample"
},
"arn": "arn:aws:s3:::my-bucket"
},
"object": {
"key": "HelloWorld.jpg",
"size": 1024,
"eTag": "d41d8cd98f00b204e9800998ecf8427e",
"versionId": "096fKKXTRTtl3on89fVO.nfljtsv6qko"
}
}
}
]
}
Shell
npm run locally
Output
> node -e "console.log(require('./index').handler({}));"
hello world
Promise { { statusCode: 200, body: '"Hello from Lambda!"' } }
You need to call your handler function from another file lets say testHandler.js in order to run via NodeJs.
This will be done like this
//import your handler file or main file of Lambda
let handler = require('./handler');
//Call your exports function with required params
//In AWS lambda these are event, content, and callback
//event and content are JSON object and callback is a function
//In my example i'm using empty JSON
handler.handlerEvent( {}, //event
{}, //content
function(data,ss) { //callback function with two arguments
console.log(data);
});
Now you can use node testHandler.js to test your handler function.
EDIT: Sample Event and content data as requested
Event:
{
"resource": "/API/PATH",
"path": "/API/PATH",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
"cache-control": "no-cache",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "IN",
"content-type": "application/json",
"Host": "url.us-east-1.amazonaws.com",
"origin": "chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
"Via": "2.0 XXXXXXXXXXXXXX.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "XXXXXXXXXX51YYoOl75RKjAWEhCyna-fuQqEBjSL96TMkFX4H0xaZQ==",
"X-Amzn-Trace-Id": "Root=1-XXX03c23-25XXXXXX948c8fba065caab5",
"x-api-key": "SECUREKEY",
"X-Forwarded-For": "XX.XX.XXX.XXX, XX.XXX.XX.XXX",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"multiValueHeaders": {
"Accept": [ "*/*" ],
"Accept-Encoding": [ "gzip, deflate, br" ],
"Accept-Language": [ "en-GB,en-US;q=0.9,en;q=0.8" ],
"cache-control": [ "no-cache" ],
"CloudFront-Forwarded-Proto": [ "https" ],
"CloudFront-Is-Desktop-Viewer": [ "true" ],
"CloudFront-Is-Mobile-Viewer": [ "false" ],
"CloudFront-Is-SmartTV-Viewer": [ "false" ],
"CloudFront-Is-Tablet-Viewer": [ "false" ],
"CloudFront-Viewer-Country": [ "IN" ],
"content-type": [ "application/json" ],
"Host": [ "apiurl.us-east-1.amazonaws.com" ],
"origin": [ "chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop" ],
"User-Agent": [ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" ],
"Via": [ "2.0 XXXXXXXXXXXXXX.cloudfront.net (CloudFront)" ],
"X-Amz-Cf-Id": [ "XXXXXXXXXhCyna-fuQqEBjSL96TMkFX4H0xaZQ==" ],
"X-Amzn-Trace-Id": [ "Root=1-XXXXXXX67339948c8fba065caab5" ],
"x-api-key": [ "SECUREAPIKEYPROVIDEDBYAWS" ],
"X-Forwarded-For": [ "xx.xx.xx.xxx, xx.xxx.xx.xxx" ],
"X-Forwarded-Port": [ "443" ],
"X-Forwarded-Proto": [ "https" ]
},
"queryStringParameters": null,
"multiValueQueryStringParameters": null,
"pathParameters": null,
"stageVariables": null,
"requestContext": {
"resourceId": "xxxxx",
"resourcePath": "/api/endpoint",
"httpMethod": "POST",
"extendedRequestId": "xxXXxxXXw=",
"requestTime": "29/Nov/2018:19:21:07 +0000",
"path": "/env/api/endpoint",
"accountId": "XXXXXX",
"protocol": "HTTP/1.1",
"stage": "env",
"domainPrefix": "xxxxx",
"requestTimeEpoch": 1543519267874,
"requestId": "xxxxxxx-XXXX-xxxx-86a8-xxxxxa",
"identity": {
"cognitoIdentityPoolId": null,
"cognitoIdentityId": null,
"apiKey": "SECUREAPIKEYPROVIDEDBYAWS",
"cognitoAuthenticationType": null,
"userArn": null,
"apiKeyId": "xxXXXXxxxxxx",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
"accountId": null,
"caller": null,
"sourceIp": "xx.xxx.xxx.xxx",
"accessKey": null,
"cognitoAuthenticationProvider": null,
"user": null
},
"domainName": "url.us-east-1.amazonaws.com",
"apiId": "xxxxx"
},
"body": "{\n \"city\": \"Test 1 City\",\n \"state\": \"NY\",\n \"zipCode\": \"11549\"\n}",
"isBase64Encoded": false
}
Content:
{
"callbackWaitsForEmptyEventLoop": true,
"logGroupName": "/aws/lambda/lambda-name",
"logStreamName": "2018/11/29/[$LATEST]xxxxxxxxxxxb",
"functionName": "lambda-name",
"memoryLimitInMB": "1024",
"functionVersion": "$LATEST",
"invokeid": "xxxxx-xxx-11e8-xxx-xxxxxxxf9",
"awsRequestId": "xxxxxx-xxxxx-11e8-xxxx-xxxxxxxxx",
"invokedFunctionArn": "arn:aws:lambda:us-east-1:xxxxxxxx:function:lambda-name"
}
In your index.js, just defined and exported a handler function, but no one calls it. In the Lambda environment, some AWS code will call this handler with message. In your local environment, you have to call your handler by yourself.
You could also have a look of this doc, it is a way to "simulate" Lambda in local environment.
You can check out lambda-local. It's a little fancier than the accepted answer above. For example, it supports passing environment variables and using JSON files for your payloads.
Perhaps the simplest way to get started, after some testing (with Node 14.17.3)
let handler = require('./index.js');
handler.handler (
{}, // event
{}, // content
(error, result) => {
if (error) console.error(JSON.stringify(error, null, 2));
else console.log(JSON.stringify(result, null, 2));
}
);
Here I am giving a solution for the general case of an synchronous call.
Function signature in your entrypoint index.js:
exports.handler = function(event, context, callback) {
Create a caller file, e.g. testIndex.js
Give it the following contents:
/**
This is a caller for ./index.js
*/
const thatIndex = require('./index');
const EVENT = {
"somekey": {
"somesubkey": "somevalue"
}
}
thatIndex.handler (
// event
EVENT,
// context
{},
// callback function with two arguments
function(err, payload) {
console.log(err);
console.log(payload);
}
);
Go back to your node console (Docker container suggested)
You may use environment variables, if your index.js needs them: define each with export MYVAR1=myvalue1
Run your caller: node testIndex.js
The console should output the payload, provided your main file (entrypoint) promise call is chained-up with: (example)
.then(() => callback(null, {
statusCode: 301,
headers: {'location': process.env.URL + '/' + somekey },
body: '',
})
)
.catch(err => callback(err))
Tip: you may need to clean up the cache with node clean cache if you notice that it's the previous code that's called.
Other Tip: in the AWS environment, you don't need to zip up the node_modules/aws-sdk folder, as it will be part of the standard execution context. But on your test machine you will need to. So, in development mode, simple run npm install, not npm install --omit=dev
So, before zipping the folder that contains your function, just remove node_modules folder recursively, and rebuild the dependencies with npm install --ommit=dev
Example of zipping the folder:
zip -r ..\resizeImage.zip .
This is what your package.json file could contain:
"dependencies": {
"sharp": "^0.27.2"
},
"devDependencies": {
"aws-sdk": "^2.1195.0"
}
If you want just to execute it locally you can use the official sam cli tool
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html
If you use VSCode you can also check out this extension:
https://marketplace.visualstudio.com/items?itemName=bogdan-onu.invoke
IMO. The best way to test a lambda is to actually just test it! What does that mean? Will simply use some testing library (like jest for example) and simply create a test on your handler function. Mock anything you need and provide some data you expect that will come into the lambda (for the event and context if needed). And that's it. You have some tests written and a quick way to test your lambda at the same time.
I am trying to create db with sequelize cli but getting this error.
ERROR: Access denied for user 'postgres'#'localhost' (using password: NO)
Here is my config.json file
{
"development": {
"username": "postgres",
"password": "postgres",
"database": "database_development",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
I can login with same user on phppgAdmin which i am currently using in config.json but its throwing the error. This is default user with all permissions and i had already created a db with ui.
Any help will be appreciated.
Okay so after messing around with config.json i figured out the solution. If you run the commands from sequalize cli documentation
sequalize cli doc then it will create the same config.json as in question. I have only modified the username and password.
The default dialect is mysql so you need to change it with postgres and it will work.
I have no idea why this is happening. We finally launched our app, everything works on the staging server, and then I deploy to an identical setup - and now this is happening - and I can't figure out why.
You can see it happening for yourself: go to http://www.crunchyserial.com/
Here is the error in the console:
Uncaught DOMException: Blocked a frame with origin "http://www.crunchyserial.com" from accessing a cross-origin frame.
at http://www.crunchyserial.com/packages/oauth/end_of_popup_response.js:18:39
at http://www.crunchyserial.com/packages/oauth/end_of_popup_response.js:37:3
If you refresh the blank oauth window, it closes and then you get this error:
and on the server log you get this:
[00.00.00.00]{"line":"431","file":"oauth.js","message":"Error in OAuth Server: Failed to complete OAuth handshake with Google. failed [400] { \"error\" : \"invalid_grant\", \"error_description\" : \"Code was already redeemed.\" }","time":{"$date":1497382695634},"level":"warn"}
[00.00.00.00]Exception while invoking method 'login' Error: Failed to complete OAuth handshake with Google. failed [400] { "error" : "invalid_grant", "error_description" : "Code was already redeemed." }
[00.00.00.00] at getTokens (packages/google-oauth/google_server.js:107:7)
at Object.getServiceData [as handleOauthRequest] (packages/google-oauth/google_server.js:81:35)
at OAuth._requestHandlers.(anonymous function) (packages/oauth2.js:27:31)
at middleware (packages/oauth.js:203:5)
at packages/oauth.js:176:5
{"line":"431","file":"oauth.js","message":"Error in OAuth Server: Failed to complete OAuth handshake with Google. failed [400] { \"error\" : \"invalid_grant\", \"error_description\" : \"Code was already redeemed.\" }","time":{"$date":1497382701056},"level":"warn"}
Exception while invoking method 'login' Error: Failed to complete OAuth handshake with Google. failed [400] { "error" : "invalid_grant", "error_description" : "Code was already redeemed." }
at getTokens (packages/google-oauth/google_server.js:107:7)
at Object.getServiceData [as handleOauthRequest] (packages/google-oauth/google_server.js:81:35)
at OAuth._requestHandlers.(anonymous function) (packages/oauth2.js:27:31)
at middleware (packages/oauth.js:203:5)
[00.00.00.00] at packages/oauth.js:176:5
Here is the settings.json I use in my ./deploy directory for the production server:
{
"public": {
"analyticsSettings": {
"Google Analytics" : {"trackingId": "//redacted//"}
}
},
"private": {
"oAuth": {
"google": {
"clientId": "//redacted//",
"secret": "//redacted//",
"loginStyle": "popup"
},
"facebook": {
"appId": "//redacted//",
"secret": "//redacted//",
"loginStyle": "popup"
},
"twitter": {
"consumerKey": "//redacted//",
"secret": "//redacted//"
"loginStyle": "popup"
}
}
}
}
Oddly enough, there is no difference between "redirect" and "popup" loginStyles... I don't notice ANY behavior difference in the app.
This is my mup.js:
module.exports = {
servers: {
one: {
host: '//redacted//',
username: 'ubuntu',
pem: "//redacted//"
// password:
// or leave blank for authenticate from ssh-agent
}
},
meteor: {
name: 'CrunchySerial',
path: '../../CrunchySerial',
servers: {
one: {}
},
buildOptions: {
serverOnly: true,
},
env: {
ROOT_URL: 'http://www.crunchyserial.com',
MONGO_URL: 'mongodb://localhost/meteor'
},
dockerImage: 'abernix/meteord:base',
deployCheckWaitTime: 400
},
mongo: {
oplog: true,
port: //redacted//,
servers: {
one: {},
},
},
};
I've tried the ROOT_URL with and without a / I've updated to all the latest versions... And I still have no idea why this is happening.
Solution: oauth, ROOT_URL, redirect_uri and www.domain.com vs domain.com
Your oauth script will only work from one domain - and when you have a ROOT_URL, it uses this in the uri that your oauth sends in it's request. Also, you have to register which redirect_uri(s) are valid with some services (like google). So oauth will only work from ONE subdomain/domain. You have to pick www or no-www - not both. I chose to keep www and setup both a DNS redirect, and a programmatic redirect (just to be sure).
DNS Redirect from # to www.[your-domain].com:
We're using NameCheap, so the URL Redirect Record was easy to setup. Here is NameCheap's documentation.
Programmatic redirect in Meteor:
This is how I force a redirect in Meteor check for www and enforce it if it's not there.
File Location: [project]/imports/startup/client/force_www_redirect.js
import { Meteor } from 'meteor/meteor'
Meteor.startup(function () {
if (location.host.indexOf('www.crunchyserial.com') !== 0) {
location = 'http://www.crunchyserial.com'
}
})
and I just make sure this is the first code my client startup sees by importing it first.
File Location: [project]/imports/startup/client/index.js
// force www.crunchyserial.com
import './force_www_redirect'
// Configure Login Buttons UI
import './login_button_configuration'
// Configure atForm
import './useraccounts_configuration'
// Run the Routes
import './routes'
which is called from [project]/client/main.js:
import '../imports/startup/client'