Logging out with Servant (Haskell) - javascript

I've been trying to implement a simple server with cookie-based authentication using Servant.
I found an example here.
Server
I created my API:
type API
= Auth '[Cookie] User :>
"login" :> ReqBody '[JSON] User :> Post '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
, Header "Set-Cookie" SetCookie ] NoContent)
:<|> "logout" :> Get '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
, Header "Set-Cookie" SetCookie ] NoContent)
Here's an implementation for the endpoints:
checkCreds :: CookieSettings
-> JWTSettings
-> Credentials -- my type storing user's login and pass
-> Handler (Headers '[Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie] NoContent)
checkCreds cookieSettings jwtSettings Credentials { credentialsUserName = userName, credentialsPassword = pass} = do
authRes <- checkIfUserExists userName pass
case authRes of
Just (name, key) -> do
mApplyCookies <- liftIO $ acceptLogin cookieSettings jwtSettings (Session key name)
return $
case mApplyCookies of
Nothing -> clearSession cookieSettings NoContent
Just applyCookies -> applyCookies NoContent
Nothing ->
throwError err401
getLogout :: CookieSettings
-> Handler (Headers '[ Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie ] NoContent)
getLogout cookieSettings = return $ clearSession cookieSettings NoContent
The cookieSettings I use are here:
cookieSettings :: CookieSettings
cookieSettings = defaultCookieSettings {
cookieIsSecure = NotSecure,
cookieSameSite = SameSiteStrict,
sessionCookieName = "MyCookie",
cookieXsrfSetting = Just def {xsrfExcludeGet = True}
}
Client
I use JavaScript fetch to poke the login endpoint:
let opts: RequestInit = {
method: "POST",
headers: new Headers({ "Content-Type": "application/json" }),
credentials: "include",
body: json,
};
fetch("http://localhost:8081/login", opts)
.then((value) => {
// Do something
}
);
This works fine and I noticed the cookies are included in the response and I can find them in Storage -> Cookies in my Firefox.
Then I use a similar method to poke the logoutendpoint:
const sendLogOut = async () => {
let resp = await fetch(Urls.logout.href, { method: "GET" });
console.log(resp.status);
};
it prints 200 in my console and I can see the cookies are included in the response:
However, nothing else happens. It seems that the response gets discarded and the cookies I had received from login are still valid.
Questions
1.) How shall I implement the logout feature properly.
2.) As I'm relatively new to web development, where can I find useful information about HTTP protocol? By "useful" I meant "something that shows examples instead of raw definitions".

I've got the answer.
In my case the problem was caused by the fact that my servant app and my JS client were technically two separate applications, hosted on two different ports (8081 and 8833 respectively).
When I set up nginx and configured routing under a single domain, everything works as expected.

Related

Why does Nuxt's proxy wont work in production mode with my setup

To avoid users know what endpoint we are requesting data, we are using #nuxtjs/proxy
This config in nuxt.config.js
const deployTarget = process.env.NUXTJS_DEPLOY_TARGET || 'server'
const deploySSR = (process.env.NUXTJS_SSR === 'true') || (process.env.NUXTJS_SSR === true)
And the proxy settings
proxy: {
'/api/**/**': {
changeOrigin: true,
target: process.env.VUE_APP_API_URL,
secure: true,
ws: false,
pathRewrite: { '^/api/': '' }
}
},
Also we deploy like so
NUXTJS_DEPLOY_TARGET=server NUXTJS_SSR=false nuxt build && NUXTJS_DEPLOY_TARGET=server NUXTJS_SSR=false nuxt start
Also in the httpClient that normally is
constructor (basePath, defaultTimeout, fetch, AbortController) {
this.basePath = basePath
this.defaultTimeout = parseInt(defaultTimeout, 10) || 1000
this.isLocalhost = !this.basePath || this.basePath.includes('localhost')
this.fetch = fetch
this.AbortController = AbortController
}
Has been modified like so:
constructor (basePath, defaultTimeout, fetch, AbortController) {
this.basePath = '/api'
this.defaultTimeout = parseInt(defaultTimeout, 10) || 1000
this.isLocalhost = !this.basePath || this.basePath.includes('localhost')
this.fetch = fetch
this.AbortController = AbortController
}
The fetch options are
_getOpts (method, options) {
const opts = Object.assign({}, options)
opts.method = opts.method || method
opts.cache = opts.cache || 'no-cache'
opts.redirect = opts.redirect || 'follow'
opts.referrerPolicy = opts.referrerPolicy || 'no-referrer'
opts.credentials = opts.credentials || 'same-origin'
opts.headers = opts.headers || {}
opts.headers['Content-Type'] = opts.headers['Content-Type'] || 'application/json'
if (typeof (opts.timeout) === 'undefined') {
opts.timeout = this.defaultTimeout
}
return opts
}
So that's making a request to https://api.anothersite.com/api/?request..
And in localhost using npm run dev its working just fine, it requests and fetchs the desired data.
But some how, when we deploy it to the staging environment all those request return
{ "code": 401, "data": "{'statusCode':401,'error':'Unauthorized','message':'Invalid token.'}", "json": { "statusCode": 401, "error": "Unauthorized", "message": "Invalid token." }, "_isJSON": true }
Note that
the front is being deployed to example.com that requires basic http authentication and we are properly authenticated
The requests in both in local and staging are done to api.example.com that doesn't require http auth where the data is served from a Strapi that doesn't need any token at all
is it posible that this response that we are getting is because as requests are from the proxy they are not http authenticated?
You should find somebody who knows some details because you will need those for that project.
Especially because here, you're hosting your app somewhere and that platform is probably missing an environment variable, hence the quite self-explanatory error
401,'error':'Unauthorized','message':'Invalid token
That also explains why that one works locally (you probably have an .env file) and not once pushed.
You could try to create a repro on an SSR-ready VPS but I'm pretty sure that #nuxtjs/proxy is working fine.
Otherwise, double checking the network requests in your browser devtools is still the way to go regarding the correct configuration of the module.
Anyway, further details are needed from your side here.
As a good practice, you should also have the following in your nuxt.config.js file
ssr: true,
target: 'server'
rather than using inline variables for those, safer and self-explanatory for everybody that way (on top of being less error-prone IMO). Or, you can use an env variable for the key itself.

Paypal Checkout client integration - Problem with returning order id promise

i have a problem integrating paypals payment gateway. I am using javascript for the client, python for the backend and the checkouts v2 api.
Creating a order on the backend works without trouble, but while waiting for my servers response the createOrder function raises a error:
unhandled_error
Object { err: "Expected an order id to be passed\nLe/</<#https://www.sandbox.paypal.com/smart/buttons?style.layout=vertical&style.color=blue&style.shape=rect&style.tagline=false&components.0=buttons&locale.country=NO&locale.lang=no&sdkMeta=eyJ1cmwiOiJodHRwczovL3d3dy5wYXlwYWwuY29tL3Nkay9qcz9jbGllbnQtaWQ9QWJmSjNNSG5oMkFIU1ZwdXl4eW5lLXBCbHdJZkNsLXpyVXc1dzFiX29TVUloZU01LXNMaDNfSWhuTnZkNUhYSW5wcXVFdm5MZG1LN0xOZ1gmZGlzYWJsZS1mdW5kaW5nPWNyZWRpdCxjYXJkIiwiYXR0cnMiOnt9fQ&clientID=AbfJ3MHnh2AHSVpuyxyne-pBlwIfCl-zrUw5w1b_oSUIheM5-sLh3_IhnNvd5HXInpquEvnLdmK7LNgX&sessionID=e2ea737589_mtc6mtu6mdi&buttonSessionID=de4bfb3626_mtc6mjm6mtk&env=sandbox&fundingEligibility=eyJwYXlwYWwiOnsiZWxpZ2libGUiOnRydWV9LCJjYXJkIjp7ImVsaWdpYmxlIjpmYWxzZSwiYnJhbmRlZCI6dHJ1ZSwidmVuZG9ycyI6eyJ2aXNhIjp7ImVsaWdpYmxlIjp0cnVlfSwibWFzdGVyY2FyZCI6eyJlbGlnaWJsZSI6dHJ1ZX0sImFtZXgiOnsiZWxpZ2libGUiOnRydWV9LCJkaXNjb3ZlciI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJoaXBlciI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJlbG8iOnsiZWxpZ2libGUiOmZhbHNlfSwiamNiIjp7ImVsaWdpYmxlIjpmYWxzZX19…", timestamp: "1593537805136", referer: "www.sandbox.paypal.com", sessionID: "e2ea737589_mtc6mtu6mdi", env: "sandbox", buttonSessionID: "de4bfb3626_mtc6mjm6mtk" }
Error: Expected an order id to be passed
Error: Expected an order id to be passed
12V21085461823829 // ticks in a few seconds later
Console screenshot
The problem seems to be that createOrder does not wait for the promise before raising the error, or that the promise is not given in the correct way. Something like that. Anyways here is the client side code:
paypal.Buttons({
// button styling removed for clarity
createOrder: function() {
// purchase information
var data = {
'track_id': vm.selectedTrack.id,
'lease_id': vm.selectedLease.id,
}
// post req to api with lease and track ids
// create payment on server side
fetch('http://localhost:5000/api/paypal/create-purchase', {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(data),
}).then(function(res) {
return res.json();
}).then(function(data) {
console.log(data.order_id)
return data.order_id
})
}
// conatiner element to render buttons in
}).render('#paypal-button');
And the server side:
#app.route('/api/paypal/create-purchase', methods=['POST'])
def paypal_create_purchase():
# cart validation removed for clarity
# create paypal purchase
environment = SandboxEnvironment(client_id=app.config['PAYPAL_PUBLIC'], client_secret=app.config['PAYPAL_PRIVATE'])
client = PayPalHttpClient(environment)
paypal_request = OrdersCreateRequest()
paypal_request.prefer('return=representation')
paypal_request.request_body (
{
"intent": "CAPTURE",
"purchase_units": [
{
"amount": {
"currency_code": "USD",
"value": lease.price
}
}
]
}
)
try:
# Call API with your client and get a response for your call
response = client.execute(paypal_request)
order = response.result
print(order.id)
except IOError as ioe:
print (ioe)
if isinstance(ioe, HttpError):
# Something went wrong server-side
print(ioe.status_code)
# note that it is the same key as on the client
return jsonify(success=True,order_id=order.id)
I found this similar thread, but i dont consider the origin of the error to be the same as in that thread (incorrect json key on client)
Also see this relevant page in the docs which supplies this code:
createOrder: function() {
return fetch('/my-server/create-paypal-transaction', {
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID; // Use the same key name for order ID on the client and server
});
}
Damn, just as i was typing out the last part of the post i noticed the error. A missing return before my fetch call. Will leave this up for other people with the same mistake.

[Python][Javascript] can't figure out how to send API calls from a NEXTjs webpage GUI to a Python Falcon backend

I'm trying to send POST requests from a NEXTjs frontend with a simple form field to a backend located on the same server which is a python script using the Falcon library. The python script itself is ran by Gunicorn and listens on the 8080 port.
Both codes run pretty well without errors but when I try to submit the form all I get is a 415 error which seems to indicate that what I'm trying to send to the API is not a supported media type but, as pointed out in this answer
Falcon has out of the box support for requests with Content-Type: application/json
Since the webpage and the server are hosted on the same VPS I've also tried to use the 127.0.0.1 address in the fetch call but that was unsuccessful as well (the backend API didn't even responded in fact)
Here's the backend code:
#!/usr/bin/env python
# coding=utf-8
import time
import falcon
import json
class Resource(object):
def on_post(self, req, resp, **kwargs):
request_body = req.media
print('POST Request: {}'.format(req))
print('Request body: {}'.format(request_body))
start = time.time()
resp.body = json.dumps({
'count_identical_pairs': count_identical_pairs(request_body),
'computation_time': int((time.time() - start) * 1000)
})
def count_identical_pairs(integers_array):
total = 0
count = dict()
# Type checking
if not isinstance(integers_array, list):
return -1
# Check if N is within the range [0..100,000]
if len(integers_array) > 100000:
return -2
for integer in integers_array:
# Check if each element of the array is within the range [−1,000,000,000..1,000,000,000]
if integer not in range(-1000000000, 1000000000):
return -3
if str(integer) not in count:
count[str(integer)] = 1
else:
count[str(integer)] += 1
for key, value in count.items():
total += value * (value - 1) / 2
return total
api = application = falcon.API()
api.add_route('/count_identical_pairs', Resource())
And here's the frontend one:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
class Index extends React.Component {
constructor() {
super();
this.state = {
input_array: [],
};
this.onSubmit = this.onSubmit.bind(this);
this.myHeaders = new Headers();
}
onChange = evt => {
// This triggers everytime the input is changed
this.setState({
[evt.target.name]: evt.target.value,
});
};
onSubmit = evt => {
evt.preventDefault();
console.log('this.state.input_array = ' + this.state.input_array);
console.log('JSON.stringify(this.state.input_array) = ' + JSON.stringify(this.state.input_array));
// Making a post request with the fetch API
// Test payload [1, 7, 7, 5, 7, 5, 6, 1]
fetch('http://vps638342.ovh.net:8080/count_identical_pairs', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8'
},
mode: 'no-cors', // Security hazard?
body: JSON.stringify(this.state.input_array),
redirect: 'follow'
})
.then(response => response.text())
.then(data => console.log('Data: ' + data))
.catch(error => console.log('Error: ' + error))
};
render() {
return (
<form onSubmit={this.onSubmit} >
<input
name="input_array"
type="text"
id="name"
value={this.state.input_array}
onChange={this.onChange}>
</input>
<input type="submit" />
</form>
);
};
}
ReactDOM.render(<Index />, document.getElementById("root"));
EDIT 1: I've tested the python backend API with Postman and I can see that it works pretty well already as you can see pictured here:
EDIT 2: Thanks to #Maku here's the update code on the backend that allows all origins, methods and header. I'm new to server development but I'm guessing it's not a very secure way to code but at least it works (I'll add a third EDIT if I find a more recommended way to do this)
Enable CORS in your falcon server and remove the 'no-cors' flag in your javascript, that worked for me the other day.
https://github.com/lwcolton/falcon-cors should work for you. To test it you could just allow all origins with something like this (I'm using another python framework so I haven't tested this exact falcon extension)
cors = CORS(allow_all_origins=True, allow_all_headers=True)
api = falcon.API(middleware=[cors.middleware])
Edit: added allow_all_headers=True like discussed in the comments.

how to fix 'Access to XMLHttpRequest has been blocked by CORS policy' Redirect is not allowed for a preflight request only one route

i'm setting a laravel and vuejs.
CORS plugin for laravel and frontend side i use Axios to call REST api
i got this ERROR
Access to XMLHttpRequest at 'https://xx.xxxx.xx' from origin
'http://localhost:8080' has been blocked by CORS policy: Response to preflight
request doesn't pass access control check: Redirect is not allowed for a
preflight request.
this is for a vuejs axios setup **main.js**
axios.defaults.baseURL = process.env.BASE_URL;
axios.defaults.headers.get['Accepts'] = 'application/json';
axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
axios.defaults.headers.common['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept';
**content.vue file**
this.loading = true;
var companyId = this.$route.params.cid;
var userId = this.$route.params.uid;
const thisVue = this;
var formData = new FormData();
let data = {};
formData.append("subject", this.title);
formData.append("content", this.content);
formData.append("posting_article_url", this.blog_link);
formData.append("recruitment_tension", this.sel_recruitment_tension);
formData.append("why_hire_engineer", this.sel_company_hire_engineer);
formData.append("technique_skill", this.requiredTechniqueSkill);
formData.append("better_technique_skill",this.betterTechniqueSkillIfThereIs);
formData.append("personality", this.requiredPersonality);
formData.append("any_request", this.anyRequest);
formData.append("location", this.location);
formData.append("supplement_time", this.supplement_time);
formData.append("supplement_contract", this.supplement_contract);
formData.append("en_benefits", this.en_benefits);
formData.append("recruit_role", this.recruit_role);
formData.append("how_to_proceed", this.how_to_proceed);
formData.append("current_structure", this.current_structure);
if (this.selectedSkill.length > 0)
{
let selectedSkills = this.selectedSkill
.filter(obj => {
return obj.id;
})
.map(item => {
return item.id;
});
formData.append("skill_keyword", selectedSkills);
}
if (this.imageBlob != "") {
formData.append("image", this.imageBlob, "temp.png");
}
for (var i = 0; i < this.sel_schedule.length; i++) {
formData.append("freelance_type[" + i + "]", this.sel_schedule[i])
}
for (var i = 0; i < this.sel_type_of_contract.length; i++) {
formData.append("contract_type[" + i + "]", this.sel_type_of_contract[i])
}
this.loading = false;
$('html, body').animate({scrollTop:300}, 'slow');
} else {
axios
.post(
"/xx/xxx/?token=" + localStorage.getItem("token"),
formData,
{
headers: [
{ "X-localization": localStorage.getItem("lan") },
{ "Access-Control-Allow-Origin": '*' },
{ "Access-Control-Allow-Headers": 'Origin, X-Requested-With, Content-Type, Accept '},
{ "Access-Control-Allow-Methods": "POST, GET, PUT, OPTIONS, DELETE" },
{ "Access-Control-Max-Age": 3600 }
]
}
)
.then(res => {
if (!res.data.result) {
if (res.data[0]) {
this.$toaster.error(res.data[0]);
this.$store.dispatch("logout");
}
if (res.data.errors) {
for (var i = 0; i < res.data.errors.length; i++) {
this.$toaster.error(res.data.errors[i].message);
}
}
this.loading = false;
} else {
this.$toaster.success(thisVue.$t("success_recruit_add"));
}
})
.catch(() => {
this.$toaster.error(thisVue.$t("err_network"));
});
}
the error occur only one route rest all are working.
also working on Postman
Permanent solution from server side:
The best and secure solution is to allow access control from server end. For laravel you can follow the following steps:
In App\Http\Middleware\Cors.php:
public function handle($request, Closure $next)
{
return $next($request)->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods','GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS');
}
In App\Http\Kernel.php:
protected $middleware = [
...
\App\Http\Middleware\Cors::class,
];
Temporary solution from browser side:
If you want to disable CORS from browser-end then follow one of the following steps:
Safari: Enable the develop menu from Preferences > Advanced. Then select “Disable Cross-Origin Restrictions” from the develop menu.
Chrome (Extension): Use the Chrome extension Allow CORS: Access-Control-Allow-Origin
Chrome (CMD): Close all your Chrome browser and services. Then run the following command:
Windows:
“C:\Program Files (x86)\Google\Chrome\Application\chrome.exe” –-allow-file-access-from-files --disable-web-security --user-data-dir --disable-features=CrossSiteDocumentBlockingIfIsolating
Mac:
open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome — args — user-data-dir=”/tmp/chrome_dev_test” — disable-web-security
The problem comes from your Vue App.
Eg: You're requesting the url below:
https://example.com/api/methods/
And the backend redirect it to:
https://example.com/api/methods
Beware of the trailing slash at the end.
The issue is from the back-end side in our case is Laravel, in your config/cors.php try to use the below config:
'supportsCredentials' => true,
'allowedOrigins' => ['*'],
'allowedOriginsPatterns' => [],
'allowedHeaders' => ['*'],
'allowedMethods' => ['*'],
'exposedHeaders' => [],
'maxAge' => 0,
Or you can try to use this code in the top of public/index.php
Edit
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE');
header('Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization, X-
Request-With');
The problem is from the server side. If you are using express js. Try to install the express cors package on your server.
npm install cors
In your app.js require cors.
var cors = require('cors')
Then, add it as a middleware to your app.
app.use(cors())
You should not experience the cors issue after installing the package.
We can fix with APP_URL, if you use it as the base url for axios request. Please, make sure your browser root url and APP_URL in .env both are same.
For example, if you run the app on "http://127.0.0.1:8000" then should be the APP_URL=http://127.0.0.1:8000
And if you run the app on "http://localhost:8000" then should be the APP_URL=http://localhost:8000
Hope, this will help! And it's tested with laravel6.x
The cors (Cross-Origin Resource Sharing) handle by server side. If you are come from laravel end so the barryvdh/laravel-cors package is help to solve this error
url:
https://packagist.org/packages/barryvdh/laravel-cors
You probably have some misconfiguration either on the webserver side or Laravel side. Perhaps this solution might help you: Why isn't my nginx web server handling ttf fonts?.
Pay close attention to the OPTIONS method, since this enables the support for Preflight.
Disabling this flag worked for me:
chrome://flags/#block-insecure-private-network-requests
nelmio_cors:
defaults:
allow_origin: ["*"]
allow_headers: ["*"]
allow_methods: ["POST", "PUT", "GET", "DELETE", "OPTIONS"]
max_age: 3600
origin_regex: false
paths:
'^/': ~
add in nelmio_cors /packge/nelmio_cors
Steps
Go to this link
https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf
download it
switch on the chrome web browser extension
Check your http link
example http to https of the remote url.
do the get api.

How to Login to MediaWiki (Wikipedia) API in Node.js

I'm trying the Wikipedia client login flow depicted in the API:Login docs, but something wrong happens:
1) I correctly get a token raised with the HTTP GET https://en.wikipedia.org/w/api.php?action=query&meta=tokens&type=login&format=json
and I get a valid logintoken string.
2.1) I then try the clientlogin like:
HTTP POST /w/api.php?action=clientlogin&format=json&lgname=xxxx&lgtoken=xxxx%2B%5C
and the POST BODY was
{
"lgpassword" : "xxxxx",
"lgtoken" : "xxxxx"
}
But I get an error:
{
"error": {
"code": "notoken",
"info": "The \"token\" parameter must be set."
},
"servedby": "mw1228"
}
If I try to change lgtoken to token I get the same result.
2.2) I have then tried the old method i.e. action=login and passing the body, but it does not work, since it gives me back another login token: HTTP POST https://en.wikipedia.org/w/api.php?action=login&format=json&lgname=xxxx
and the same POST BODY
I then get
{
"warnings": {}
},
"login": {
"result": "NeedToken",
"token": "xxxxx+\\"
}
where the docs here states that
NeedToken if the lgtoken parameter was not provided or no session was active (e.g. your cookie handling is broken).
but I have passed the lgtoken in the json body as showed.
I'm using Node.js and the built-in http module, that is supposed to pass and keep session Cookies in the right way (with other api it works ok).
I have found a similar issue on a the LrMediaWiki client here.
[UPDATE]
This is my current implementation:
Wikipedia.prototype.loginUser = function (username, password) {
var self = this;
return new Promise((resolve, reject) => {
var cookies = self.cookies({});
var headers = {
'Cookie': cookies.join(';'),
'Accept': '*/*',
'User-Agent': self.browser.userAgent()
};
// fetch login token
self.api.RequestGetP('/w/api.php', headers, {
action: 'query',
meta: 'tokens',
type: 'login',
format: 'json'
})
.then(response => { // success
if (response.query && response.query.tokens && response.query.tokens['logintoken']) {
self.login.logintoken = response.query.tokens['logintoken'];
self.logger.info("Wikipedia.login token:%s", self.login);
return self.api.RequestPostP('/w/api.php', headers, {
action: 'login',
format: 'json',
lgname: username
},
{
lgpassword: password,
lgtoken: self.login.logintoken
});
} else {
var error = new Error('no logintoken');
return reject(error);
}
})
.then(response => { // success
return resolve(response);
})
.catch(error => { // error
self.logger.error("Wikipedia.login error%s\n%#", error.message, error.stack);
return reject(error);
});
});
}//loginUser
where this.api is a simple wrapper of the Node.js http, the source code is available here and the api signatures are like:
Promise:API.RequestGetP(url,headers,querystring)
Promise:API.RequestPostP(url,headers,querystring,body)
If the currently accepted answer isn't working for someone, the following method will definitely work. I've used the axios library to send requests. Any library can be used but the key lies in formatting the body and headers correctly.
let url = "https://test.wikipedia.org/w/api.php";
let params = {
action: "query",
meta: "tokens",
type: "login",
format: "json"
};
axios.get(url, { params: params }).then(resp => {
let loginToken = resp.data.query.tokens.logintoken
let cookie = resp.headers["set-cookie"].join(';');
let body = {
action: 'login',
lgname: 'user_name',
lgpassword: 'password',
lgtoken: loginToken,
format: 'json'
}
let bodyData = new URLSearchParams(body).toString();
axios.post(url, bodyData, {
headers: {
Cookie: cookie,
}
}).then(resp => {
// You're now logged in!
// You'll have to add the following cookie in the headers again for any further requests that you might make
let cookie = resp.headers["set-cookie"].join(';')
console.log(resp.data)
})
})
And you should be seeing a response like
{
login: { result: 'Success', lguserid: 0000000, lgusername: 'Username' }
}
The second post request was where I got stuck for several hours, trying to figure out what was wrong. You need to send the data in an encoded form by using an API like URLSearchParams, or by just typing up the body as a string manually yourself.
I think from what you are saying you have lgtoken and lgname in the URL you are using, and then lgpassword and lgtoken (again!) in a JSON-encoded POST body.
This is not how the Mediawiki API works.
You submit it all as POST parameters. JSON is never involved, except when you ask for the result to come back in that format. I can't help you fix your code as you don't provide it, but that's what you need to do. (If you edit your question with your code, I'll do my best to help you.)
After seeing your code, I'll presume (without knowing the detail of your code) that you want something like this:
return self.api.RequestPostP('/w/api.php', headers, {
action: 'login',
format: 'json',
lgname: username,
lgpassword: password,
lgtoken: self.login.logintoken
});

Categories