Related
Intro
I am developing an login application using Nextjs on frontend and springboot on backend.
Problem
I am able to login from the frontend which calls the loginAPI named /authenticate developed in springboot and it is successfully returning the authToken.After login I got redirects to home (/allcoupons) page.
In login , I am passing the value of token as response.data like this
const handleSubmit = async (e) => {
// e.preventDefault();
let requestbody = {
username: credentials.username,
password: credentials.password,
};
try {
const response = await axios({
method: "post",
headers: {},
url: "http://localhost:8081/authenticate",
data: requestbody,
});
//console.log("credentials for login = ", credentials);
//console.log("response from api = ", response);
if (response.status === 200) {
router.push(
{
pathname: "/allcoupons",
query: { auth: JSON.stringify(response.data) },
},
undefined,
{
shallow: true
}
);
} else alert("invalid credentials");
} catch (error) {
setTimeout(() => {
setShowAlert(true);
}, 2000);
setShowAlert(false);
}
};
Then I redirects to /allcoupons component
In this component I am using getServerSideProps(). I am able to access the authtoken value from context.query.auth but now I am unable to send this authtoken value as bearer token with another API /allcoupons in the headers.
home.js
import React, { Fragment, useState, useEffect } from "react";
import { useRouter } from "next/router";
import Footer from "./components/footer";
import { Alert } from "flowbite-react";
import axios from "axios";
const Allcoupons = ({ datafromAPI }) => {
const router = useRouter();
//console.log("datafromAPI in components = ", datafromAPI.coupons)
//let data = datafromAPI.coupons;
const val = router.query.auth ? JSON.parse(router.query.auth) : {};
console.log("authtoken form login = ", val);
const [showModal, setShowModal] = useState(false);
.
.
return (
<>
.
.
.
</>
);
};
export async function getServerSideProps(context) {
//Fetch data from get API
//console.log("context = ", context.query.auth);
const token = context.query.auth;
console.log("token = ",context.query.auth)
const res = await axios.get(
`http://localhost:8081/couponstore/v1.0/coupons`,
//using like this
{ headers: { Authorization: `Bearer ${token}` } }
);
const datafromAPI = res.data;
console.log("data from API server = ",datafromAPI);
return { props: { datafromAPI } };
}
Observation
But as soon as I redirects to this page , it endpoint change from /allcoupons to /allcoupons?auth={authtokenvalue} .
Logs/StackTrace
Also this log comes in the terminal
token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"
error - [AxiosError: Request failed with status code 403] {
code: 'ERR_BAD_REQUEST',
config: {
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false
},
adapter: [Function: httpAdapter],
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
env: { FormData: [Function] },
validateStatus: [Function: validateStatus],
headers: {
Accept: 'application/json, text/plain, */*',
Authorization: 'Bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"',
'User-Agent': 'axios/0.27.2'
},
method: 'get',
url: 'http://localhost:8081/couponstore/v1.0/coupons',
data: undefined
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype] {
abort: [Function (anonymous)],
aborted: [Function (anonymous)],
connect: [Function (anonymous)],
error: [Function (anonymous)],
socket: [Function (anonymous)],
timeout: [Function (anonymous)],
prefinish: [Function: requestOnPrefinish]
},
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
_closed: false,
socket: Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: 'localhost',
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 7,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: false,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Circular *1],
write: [Function: writeAfterFIN],
[Symbol(async_id_symbol)]: 285969,
[Symbol(kHandle)]: [TCP],
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: false,
[Symbol(kSetKeepAlive)]: true,
[Symbol(kSetKeepAliveInitialDelay)]: 60,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0,
[Symbol(RequestTimeout)]: undefined
},
_header: 'GET /couponstore/v1.0/coupons HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Authorization: Bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"\r\n' +
'User-Agent: axios/0.27.2\r\n' +
'Host: localhost:8081\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: nop],
agent: Agent {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: [Object: null prototype],
requests: [Object: null prototype] {},
sockets: [Object: null prototype],
freeSockets: [Object: null prototype] {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
scheduling: 'lifo',
maxTotalSockets: Infinity,
totalSocketCount: 1,
[Symbol(kCapture)]: false
},
socketPath: undefined,
method: 'GET',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/couponstore/v1.0/coupons',
_ended: true,
res: IncomingMessage {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 4,
_maxListeners: undefined,
socket: [Socket],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
rawHeaders: [Array],
rawTrailers: [],
aborted: false,
upgrade: false,
url: '',
method: null,
statusCode: 403,
statusMessage: '',
client: [Socket],
_consuming: false,
_dumped: false,
req: [Circular *1],
responseUrl: 'http://localhost:8081/couponstore/v1.0/coupons',
redirects: [],
[Symbol(kCapture)]: false,
[Symbol(kHeaders)]: [Object],
[Symbol(kHeadersCount)]: 18,
[Symbol(kTrailers)]: null,
[Symbol(kTrailersCount)]: 0,
[Symbol(RequestTimeout)]: undefined
},
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'localhost',
protocol: 'http:',
_redirectable: Writable {
_writableState: [WritableState],
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
_options: [Object],
_ended: true,
_ending: true,
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 0,
_requestBodyBuffers: [],
_onNativeResponse: [Function (anonymous)],
_currentRequest: [Circular *1],
_currentUrl: 'http://localhost:8081/couponstore/v1.0/coupons',
[Symbol(kCapture)]: false
},
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] {
accept: [Array],
authorization: [Array],
'user-agent': [Array],
host: [Array]
},
[Symbol(kUniqueHeaders)]: null
},
response: {
status: 403,
statusText: '',
headers: {
'x-content-type-options': 'nosniff',
'x-xss-protection': '1; mode=block',
'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
pragma: 'no-cache',
expires: '0',
'x-frame-options': 'DENY',
'content-length': '0',
date: 'Fri, 30 Sep 2022 10:27:40 GMT',
connection: 'close'
},
config: {
transitional: [Object],
adapter: [Function: httpAdapter],
transformRequest: [Array],
transformResponse: [Array],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
env: [Object],
validateStatus: [Function: validateStatus],
headers: [Object],
method: 'get',
url: 'http://localhost:8081/couponstore/v1.0/coupons',
data: undefined
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype],
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
_closed: false,
socket: [Socket],
_header: 'GET /couponstore/v1.0/coupons HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Authorization: Bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"\r\n' +
'User-Agent: axios/0.27.2\r\n' +
'Host: localhost:8081\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: nop],
agent: [Agent],
socketPath: undefined,
method: 'GET',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/couponstore/v1.0/coupons',
_ended: true,
res: [IncomingMessage],
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'localhost',
protocol: 'http:',
_redirectable: [Writable],
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype],
[Symbol(kUniqueHeaders)]: null
},
data: ''
},
page: '/allcoupons'
}
Whats the mistake here??
This how do usually when I create react apps
I create a global context
import { createContext } from "react";
import { GlobalContextType } from "../types";
const defaultValue: GlobalContextType = {
dispatch: () => {},
state: {
user: { authToken: "", username: "" }
},
};
export const GlobalContext = createContext(defaultValue);
create a hook for my global context
import { useContext } from "react";
import { GlobalContext } from "../contexts";
export function useGlobalContext () {
const context = useContext(GlobalContext)
if (!context) throw new Error("Missing Context - Global context");
return context;
}
Create state reducer hook
import { Dispatch, useMemo, useReducer } from "react";
import {
GlobalContextActions,
GlobalContextState,
} from "../types";
export function globalContextReducer(
state: GlobalContextState,
action: ReducerAction<GlobalContextActions>
): GlobalContextState {
switch (action.type) {
case GlobalContextActions.setUser:
return {
...state,
user: action.payload
};
default:
return state;
}
}
export function useGlobalContextReducer(reducer = globalContextReducer) {
const [state, dispatch] = useReducer(reducer, {
user : {authToken: "", username: ""}
});
return useMemo(() => {
return {
dispatch,
state,
};
}, [d, state]);
}
Wrapped the whole App with my global context
import { GlobalContext } from "../src/contexts";
import { useGlobalContextReducer } from "../src/hooks/use-global-context-reducer";
export default function MyApp(props: MyAppProps) {
const { Component pageProps } = props;
const globalContextReducer = useGlobalContextReducer();
return (
<GlobalContext.Provider value={globalContextReducer}>
<Component {...pageProps} />
</GlobalContext.Provider>
);
}
this just my setup
then in my login page
import {GlobalContextActions, useGlobalContext} from "../../context"
import {Routes} from "../routes"
function Login() {
const { dispatch} = useGlobalContext();
const router = useRouter();
const [form, updateForm] = useState({
email: "",
password: "",
});
const onFieldChange = (event: ChangeEvent<HTMLInputElement>) => {
const { target } = event;
const { id, value } = target;
updateForm((current) => ({ ...current, [id]: value }));
};
const isLoginDisabled = !validateEmail(form.email) || !form.password;
const onLogin = async () => {
const dataService = container.resolve(DataService);
const result = await dataService.getLoginData(form.email, form.password);
if (result.error) {
return dispatchError(result?.errorMessage as string);
}
useGlobalContextReducer({action:GlobalContextActions.setUser, payload: result.data});
router.push(Routes.home);
};
return (
<Box>
<form action="" onSubmit={(event) => event?.preventDefault()}>
<TextField
id="email"
label={"email"}
variant="standard"
required
value={form.email}
onChange={onFieldChange}
type="email"
fullWidth
data-testid="email"
/>
<TextField
id="password"
label={"password"}
variant="standard"
type="password"
value={form.password}
required
fullWidth
onChange={onFieldChange}
data-testid="password"
/>
<Button type="submit" variant="contained" disabled={isLoginDisabled} onClick={onLogin}>
Login
</Button>
</form>
</Box>
);
}
export default Login;
and this how I use the global state
function MyPage() {
const {state} = useGlobalContext
return (<div>{JSON.stringinfy(state)}</div>)
}
First of all I do not recommend returning authtoken in response of successful login. You should use httpOnly cookies to set authtoken. It is more secure and you can set it from backend like this.
Second axios provide a way to set Authorization token in request. I mean you can alter default headers like below:
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
Read more in doc
Trying to send an image through GupShup. I'm using their sandbox. My backend is node.js with feathersjs.
But it's returning me this error:
Response {
size: 0,
timeout: 0,
[Symbol(Body internals)]: {
body: PassThrough {
_readableState: [ReadableState],
readable: true,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_writableState: [WritableState],
writable: false,
allowHalfOpen: true,
_transformState: [Object],
[Symbol(kCapture)]: false
},
disturbed: false,
error: null
},
[Symbol(Response internals)]: {
url: 'https://api.gupshup.io/sm/api/v1/msg',
status: 400,
statusText: 'Bad Request',
headers: Headers { [Symbol(map)]: [Object: null prototype] },
counter: 0
}
}
This is the code to send the image
const form = new URLSearchParams();
form.append('channel', 'whatsapp');
form.append('destination', destination);
form.append('source', app.get('GUPSHUP_NUMBER'));
form.append('message.payload', JSON.stringify(message));
form.append('src.name', 'OneAccess');
console.log(form);
try {
const res = await fetch('https://api.gupshup.io/sm/api/v1/msg', {
method: 'POST',
body: form,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
apikey: app.get('GUPSHUP_API'),
},
});
console.log('result message', res);
} catch (err) {
console.log('errro sending msg', err);
}
This is the message that I'm trying to send
message: {
type: 'image',
originalUrl:
'https://images.pexels.com/photos/248797/pexels-photo-248797.jpeg',
previewUrl:
'https://images.pexels.com/photos/248797/pexels-photo-248797.jpeg',
caption: 'Sample image',
},
Can anyone help me?
Change message.payload to message. You can check the correct signature at https://www.gupshup.io/developer/docs/bot-platform/guide/whatsapp-api-documentation#SendImage
Hi I am writing a unit test case of my function. this function returns a json response in case request is successful else return an error.
describe("Operations", async () => {
it("user info", async () => {
let accessToken = 'dfsf;
const result = await authConnectors.getUser(accessToken);
});
});
How to assert http error and response using chai in nodejs ?
{ Error: Unauthorized
at Request.callback (/vagrant/node_modules/superagent/lib/node/index.js:699:13)
at IncomingMessage.parser (/vagrant/node_modules/superagent/lib/node/index.js:903:18)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
status: 401,
response:
Response {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
res:
IncomingMessage {
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 4,
_maxListeners: undefined,
socket: [Object],
connection: [Object],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers: [Object],
rawHeaders: [Object],
trailers: {},
rawTrailers: [],
upgrade: false,
url: '',
method: null,
statusCode: 401,
statusMessage: 'Unauthorized',
client: [Object],
_consuming: true,
_dumped: false,
req: [Object],
text: '',
read: [Function] },
request:
Request {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
_agent: false,
_formData: null,
method: 'GET',
url: 'https://id-dev',
_header: [Object],
header: [Object],
writable: true,
_redirects: 0,
_maxRedirects: 5,
cookies: '',
qs: {},
_query: [],
qsRaw: [],
_redirectList: [],
_streamRequest: false,
req: [Object],
protocol: 'https:',
host: 'id-dh',
_endCalled: true,
_callback: [Function],
res: [Object],
response: [Circular],
called: true },
req:
ClientRequest {
domain: null,
_events: [Object],
_eventsCount: 3,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
upgrading: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedHeader: {},
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Object],
connection: [Object],
_header: 'GET oqcnM_4xWNbmv-WaoJ63UOjcZSHW-KrULLRUFzXrQKS6iuPWp6poHLjop1WsRtw5Y-KQCTZVI783awnn3_utEw6yqD6qpHHZwhxiPeM8mFZgN_zH4xX0ChbQ3ayao9Ms6ZHQT0X00N467HPfq5hVAW4D3a-3DURwnUvvVTIsYTShhZAsag2P26WDbKapPdgfVpjQKV-4GP--cLWbAZpEWJT3yS31YiY-VEcdfjdDU\r\nAccept: application/json\r\nConnection: close\r\n\r\n',
_headers: [Object],
_headerNames: [Object],
_onPendingData: null,
agent: [Object],
socketPath: undefined,
timeout: undefined,
method: 'GET',
path: '/userinfo',
_ended: true,
parser: null,
res: [Object] },
text: '',
body: {},
files: undefined,
buffered: true,
headers:
{ 'content-length': '0',
'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
'x-xss-protection': '1; mode=block',
'x-content-type-options': 'nosniff',
'content-security-policy': 'default-src \'self\';script-src \'self\' https://track.abc.net;style-src \'self\' \'unsafe-inline\' https://track.abc.net;img-src \'self\' https://track.abc.net;frame-src \'self\' https://track.abc.net;object-src \'none\';',
'www-authenticate': 'Bearer error="invalid_token"',
date: 'Wed, 28 Mar 2018 11:57:56 GMT',
connection: 'close' },
header:
{ 'content-length': '0',
'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
'x-xss-protection': '1; mode=block',
'x-content-type-options': 'nosniff',
'content-security-policy': 'default-src \'self\';script-src \'self\' https://track.abc.net;style-src \'self\' \'unsafe-inline\' https://track.abc.net;img-src \'self\' https://track.abc.net;frame-src \'self\' https://track.abc.net;object-src \'none\'',
'www-authenticate': 'Bearer error="invalid_token"',
date: 'Wed, 28 Mar 2018 11:57:56 GMT',
connection: 'close' },
statusCode: 401,
status: 401,
statusType: 4,
info: false,
ok: false,
redirect: false,
clientError: true,
serverError: false,
error:
{ Error: cannot GET /sts/connect/userinfo (401)
at Response.toError (/vagrant/node_modules/superagent/lib/node/response.js:94:15)
at ResponseBase._setStatusProperties (/vagrant/node_modules/superagent/lib/response-base.js:123:16)
at new Response (/vagrant/node_modules/superagent/lib/node/response.js:41:8)
at Request._emitResponse (/vagrant/node_modules/superagent/lib/node/index.js:739:20)
at IncomingMessage.parser (/vagrant/node_modules/superagent/lib/node/index.js:903:38)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
status: 401,
text: '',
method: 'GET',
path: '/sts' },
accepted: false,
noContent: false,
badRequest: false,
unauthorized: true,
notAcceptable: false,
forbidden: false,
notFound: false,
type: '',
links: {},
setEncoding: [Function: bound ],
redirects: [] } }
Using expect and should you can test as below:
describe('Testing https responses', () => {
it("user info", async (done) => {
.... request here...
.catch((err) => {
err.response.should.have.status(404);
err.response.body.should.have.property('error');
err.response.body.error.should.eql('User could not be found');
done()
});
})
it('testing using expect', (done) => {
request.get('http://localhost:8000', function (err, res, body) {
expect(res.statusCode).to.equal(400);
expect(res).to.have.property('body');
expect(res.body).to.equal('wrong header');
done();
});
});
})
These tests are asynchronous, so they need the promise to be solved at the end with done()
Edit - Added once more example
.property(name[, val[, msg]])
Asserts that the target has a property with the given key name.
expect({a: 1}).to.have.property('a');
Here three links that can be helpful:
How to properly test for HTTP failure responses
Testing HTTP Responses in Node.js
Michael Herman - Testing Node.js with Mocha and Chai
Chai Doc - method property using expect
You could use try-catch and handle errors =)
it("user info", async () => {
try{
let accessToken = 'dfsf;
const result = await authConnectors.getUser(accessToken);
} catch(e) {
// check error
}
});
I am trying to authorise my server for calling the youtube analytics api via the module "googleapis" (https://www.npmjs.com/package/googleapis). I want to use the JWT to authorise. As you can see I get a forbidden error back from the api.
I read a ton of posts and tinkered around for a whole while, but I can't figure out what is wrong.
Is my code ok? Otherwise it as to be a google cloud related issue. (E.g. I did not set up the roles properly there.)
import fs from 'fs';
import { google } from 'googleapis';
// read credentials from file
const credentialsFromFile = fs.readFileSync(`${__dirname}/../credentials.json`);
const credentials = JSON.parse(credentialsFromFile.toString());
const scopes = [
'https://www.googleapis.com/auth/youtube.readonly',
'https://www.googleapis.com/auth/yt-analytics-monetary.readonly',
'https://www.googleapis.com/auth/youtubepartner',
];
// get an authorized client
const jwtClient = new google.auth.JWT(
credentials.client_email,
null,
credentials.private_key,
scopes
);
const youtubeAnalyticsClient = google.youtubeAnalytics({
version: 'v1',
auth: jwtClient,
});
function runQuery(callback) {
youtubeAnalyticsClient.reports.query(
{
auth: jwtClient,
ids: 'channel==MINE',
'start-date': '2018-01-01',
'end-date': '2018-02-01',
metrics: 'views',
},
(error, result) => {
if (error) console.log(error.errors);
else {
console.log(result);
callback(result);
}
}
);
}
jwtClient.authorize((error, result) => {
if (error) console.log(error);
else {
/*
{ access_token: '{THE ACCESS TOKEN}',
token_type: 'Bearer',
expiry_date: 1522070090000,
refresh_token: 'jwt-placeholder' } */
console.log(result);
runQuery(() => {
// worked :)
});
}
});
The response object may be helpful:
response:
{ status: 403,
statusText: 'Forbidden',
headers:
{ vary: 'X-Origin, Origin,Accept-Encoding',
'content-type': 'application/json; charset=UTF-8',
date: 'Mon, 26 Mar 2018 10:39:33 GMT',
expires: 'Mon, 26 Mar 2018 10:39:33 GMT',
'cache-control': 'private, max-age=0',
'x-content-type-options': 'nosniff',
'x-frame-options': 'SAMEORIGIN',
'x-xss-protection': '1; mode=block',
server: 'GSE',
'alt-svc': 'hq=":443"; ma=2592000; quic=51303432; quic=51303431; quic=51303339; quic=51303335,quic=":443"; ma=2592000; v="42,41,39,35"',
'accept-ranges': 'none',
connection: 'close' },
config:
{ adapter: [Function: httpAdapter],
transformRequest: [Object],
transformResponse: [Object],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: 2147483648,
validateStatus: [Function],
headers: [Object],
method: 'get',
url: 'https://www.googleapis.com/youtube/analytics/v1/reports',
paramsSerializer: [Function],
data: undefined,
params: [Object] },
request:
ClientRequest {
domain: null,
_events: [Object],
_eventsCount: 6,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: false,
_last: true,
upgrading: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Object],
connection: [Object],
_header: 'GET /youtube/analytics/v1/reports?ids=channel%3D%3DMINE&start-date=2018-01-01&end-date=2018-02-01&metrics=views HTTP/1.1\r\nAccept: application/json, text/plain, */*\r\nAuthorization: Bearer ya29.c.El6KBUokXWf8yjN1LXk04b3FTGa1jadNEmvbyLBQfVmK9KIbxwxA6e88m_OujuTcyrJW60ojwnKiNfH-9E2iegmwlDOdKI8VxORrniGIt9gc_FGp2tvi2GLabzTri74b\r\nUser-Agent: google-api-nodejs-client/1.3.2\r\nHost: www.googleapis.com\r\nConnection: close\r\n\r\n',
_onPendingData: [Function: noopPendingOutput],
agent: [Object],
socketPath: undefined,
timeout: undefined,
method: 'GET',
path: '/youtube/analytics/v1/reports?ids=channel%3D%3DMINE&start-date=2018-01-01&end-date=2018-02-01&metrics=views',
_ended: true,
res: [Object],
aborted: undefined,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
_redirectable: [Object],
[Symbol(outHeadersKey)]: [Object] },
data: { error: [Object] } },
code: 403,
errors: [ { domain: 'global', reason: 'forbidden', message: 'Forbidden' } ] }
Update 1
I updated the code a little, so that is analog to this example implementation: https://github.com/google/google-api-nodejs-client/blob/HEAD/samples/jwt.js
I get an authorised client as seen in the code, but the same error occurs. I am pretty sure I messed up the settings in the google cloud settings. Is it necessary to do the domain verification at google cloud when developing locally?
Update 2
Even with OAuth2 Authentication (passing a code as cli arg after allowing scopes in the browser) I get the forbidden-error. Seems like I need to enable it in the cloud dev console, but how do I do that?
Hi I am trying to get a response via a http using the callback method. But I get a lot of information but not my data :
Request {
domain: null,
_events:
{ error: [Function: bound ],
complete: [Function: bound ],
pipe: [Function] },
_eventsCount: 3,
_maxListeners: undefined,
uri:
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'localhost',
port: 80,
hostname: 'localhost',
hash: null,
search: '?filter=route_short_name',
query: 'filter=route_short_name',
pathname: '/php-rest/api.php/routes',
path: '/php-rest/api.php/routes?filter=route_short_name',
href: 'http://localhost/php-rest/api.php/routes?filter=route_short_name' },
callback: [Function],
readable: true,
writable: true,
_qs:
Querystring {
request: [Circular],
lib: { formats: [Object], parse: [Function], stringify: [Function] },
useQuerystring: undefined,
parseOptions: {},
stringifyOptions: {} },
_auth:
Auth {
request: [Circular],
hasAuth: false,
sentAuth: false,
bearerToken: null,
user: null,
pass: null },
_oauth: OAuth { request: [Circular], params: null },
_multipart:
Multipart {
request: [Circular],
boundary: '9ab5d31f-9896-4fb9-8f89-47e5501e9342',
chunked: false,
body: null },
_redirect:
Redirect {
request: [Circular],
followRedirect: true,
followRedirects: true,
followAllRedirects: false,
followOriginalHttpMethod: false,
allowRedirect: [Function],
maxRedirects: 10,
redirects: [],
redirectsFollowed: 0,
removeRefererHeader: false },
_tunnel:
Tunnel {
request: [Circular],
proxyHeaderWhiteList:
[ 'accept',
'accept-charset',
'accept-encoding',
'accept-language',
'accept-ranges',
'cache-control',
'content-encoding',
'content-language',
'content-location',
'content-md5',
'content-range',
'content-type',
'connection',
'date',
'expect',
'max-forwards',
'pragma',
'referer',
'te',
'user-agent',
'via' ],
proxyHeaderExclusiveList: [] },
headers: { host: 'localhost' },
setHeader: [Function],
hasHeader: [Function],
getHeader: [Function],
removeHeader: [Function],
method: 'GET',
localAddress: undefined,
pool: {},
dests: [],
__isRequestRequest: true,
_callback: [Function],
proxy: null,
tunnel: false,
setHost: true,
originalCookieHeader: undefined,
_disableCookies: true,
_jar: undefined,
port: 80,
host: 'localhost',
path: '/php-rest/api.php/routes?filter=route_short_name',
httpModule:
{ IncomingMessage: { [Function: IncomingMessage] super_: [Object] },
METHODS:
[ 'ACL',
'BIND',
'CHECKOUT',
'CONNECT',
'COPY',
'DELETE',
'GET',
'HEAD',
'LINK',
'LOCK',
'M-SEARCH',
'MERGE',
'MKACTIVITY',
'MKCALENDAR',
'MKCOL',
'MOVE',
'NOTIFY',
'OPTIONS',
'PATCH',
'POST',
'PROPFIND',
'PROPPATCH',
'PURGE',
'PUT',
'REBIND',
'REPORT',
'SEARCH',
'SUBSCRIBE',
'TRACE',
'UNBIND',
'UNLINK',
'UNLOCK',
'UNSUBSCRIBE' ],
OutgoingMessage: { [Function: OutgoingMessage] super_: [Object] },
ServerResponse: { [Function: ServerResponse] super_: [Object] },
STATUS_CODES:
{ '100': 'Continue',
'101': 'Switching Protocols',
'102': 'Processing',
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'207': 'Multi-Status',
'208': 'Already Reported',
'226': 'IM Used',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'307': 'Temporary Redirect',
'308': 'Permanent Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed',
'406': 'Not Acceptable',
'407': 'Proxy Authentication Required',
'408': 'Request Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Failed',
'413': 'Payload Too Large',
'414': 'URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Range Not Satisfiable',
'417': 'Expectation Failed',
'418': 'I\'m a teapot',
'421': 'Misdirected Request',
'422': 'Unprocessable Entity',
'423': 'Locked',
'424': 'Failed Dependency',
'425': 'Unordered Collection',
'426': 'Upgrade Required',
'428': 'Precondition Required',
'429': 'Too Many Requests',
'431': 'Request Header Fields Too Large',
'451': 'Unavailable For Legal Reasons',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
'506': 'Variant Also Negotiates',
'507': 'Insufficient Storage',
'508': 'Loop Detected',
'509': 'Bandwidth Limit Exceeded',
'510': 'Not Extended',
'511': 'Network Authentication Required' },
Agent: { [Function: Agent] super_: [Object], defaultMaxSockets: Infinity },
globalAgent:
Agent {
domain: null,
_events: [Object],
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: [Object],
requests: {},
sockets: {},
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256 },
ClientRequest: { [Function: ClientRequest] super_: [Object] },
request: [Function],
get: [Function],
_connectionListener: [Function: connectionListener],
Server: { [Function: Server] super_: [Object] },
createServer: [Function],
Client: [Function: deprecated],
createClient: [Function: deprecated] },
agentClass:
{ [Function: Agent]
super_:
{ [Function: EventEmitter]
EventEmitter: [Circular],
usingDomains: false,
defaultMaxListeners: [Getter/Setter],
init: [Function],
listenerCount: [Function] },
defaultMaxSockets: Infinity },
agent:
Agent {
domain: null,
_events: { free: [Function] },
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: { path: null },
requests: {},
sockets: {},
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256 } }
My Node JS code is :
var http = require('http');
function getRoutes(callback){
var callback = request('http://localhost/php-rest/api.php/routes?filter=route_short_name', function(error, response, body, callback) {
if (!error && response.statusCode == 200) {
result = JSON.stringify(JSON.parse(body));
//console.log(result);
//res.setHeader('Content-Type', 'application/json');
//res.send(result);
return callback = result;
} else {
//res.end('Error: ' + error);
return callback = error;
}
});
return callback;
}
app.get('/getRoutes', function(req, res) {
var data = getRoutes();
console.log(data);
//res.setHeader('Content-Type', 'application/json');
res.send(data);
});
I want the callback (content of result or error).
If anyone can help me on this it would be greatly appreciated.
This is how you should write your function using callback. request module doesn't have a callback param, see here.
var http = require('http');
function getRoutes(callback){
request('http://localhost/php-rest/api.php/routes?filter=route_short_name', function(error, response, body) {
if (!error && response.statusCode == 200) {
result = JSON.stringify(JSON.parse(body));
return callback(result, false);
} else {
return callback(null, error);;
}
});
}
app.get('/getRoutes', function(req, res) {
getRoutes(function(err, data){
if(err) return res.send(err);
res.send(data);
});
});
You have issues with callback in your code. I think you should learn how to use callbacks. here is tutorial for you https://www.tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm
var http = require('http');
function getRoutes(callback){
request('http://localhost/php-rest/api.php/routes?filter=route_short_name', function(error, response, body) {
if (!error && response.statusCode == 200) {
result = JSON.stringify(JSON.parse(body));
return callback(null, result);
} else {
return callback(error, null);
}
});
}
app.get('/getRoutes', function(req, res) {
getRoutes(function(err, data){
if(!err){
res.send(data);
}
else{
res.send(err);
}
});
});
You didn't do the callbacks right (oops im slow)
var request = require('request');
function getRoutes(callback){
request('http://localhost/php-rest/api.php/routes?filter=route_short_name', (error, response, body) => {
if (!error && response.statusCode == 200) {
result = JSON.stringify(JSON.parse(body));
callback(null, result);
} else {
callback(error, null);
}
});
};
app.get('/getRoutes', (req, res) => {
getRoutes(function(err, data) {
if (err) console.log('error', err)//error handling
console.log(data);
});
});