My login component briefly displays before being removed by an error message about an undefined object in a promise.
Here is the promise definition:
static init(): Promise<any> {
KeycloakClientService.auth.loggedIn = false;
return new Promise((resolve, reject) => {
const keycloakConfig = {
url: environment.KEYCLOAK_URL,
realm: environment.KEYCLOAK_REALM,
clientId: environment.KEYCLOAK_CLIENTID,
'ssl-required': 'external',
'public-client': true
};
const keycloakAuth: any = new Keycloak(keycloakConfig);
keycloakAuth.init({onLoad: 'check-sso'})
.success(() => {
KeycloakClientService.auth.loggedIn = true;
KeycloakClientService.auth.authz = keycloakAuth;
KeycloakClientService.auth.logoutUrl = environment.KEYCLOAK_URL
+ '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
+ document.baseURI;
console.log('=======>> The keycloak client has been initiated successfully');
resolve('Succeeded in initiating the keycloak client');
})
.error(() => {
reject('Failed to initiate the keycloak client');
});
});
}
It is called by:
KeycloakClientService.init()
.then(
() => {
console.log('The keycloak client has been initialized');
}
)
.catch(
(error) => {
console.log(error);
window.location.reload();
}
);
The console shows both messages:
The keycloak client has been initiated successfully
The keycloak client has been initialized
I'm using Angular 6.0.4 and tried following this blog
Any way to work around this error so as to keep my login form displayed ?
UPDATE: I tried using an observable instead of a promise but the issue remained the same:
public init(): Observable<any> {
KeycloakClientService.auth.loggedIn = false;
return new Observable((observer) => {
const keycloakConfig = {
'url': environment.KEYCLOAK_URL,
'realm': environment.KEYCLOAK_REALM,
'clientId': environment.KEYCLOAK_CLIENTID,
'ssl-required': 'external',
'public-client': true
};
const keycloakAuth: any = new Keycloak(keycloakConfig);
keycloakAuth.init({ 'onLoad': 'check-sso' })
.success(() => {
KeycloakClientService.auth.loggedIn = true;
KeycloakClientService.auth.authz = keycloakAuth;
KeycloakClientService.auth.logoutUrl = environment.KEYCLOAK_URL
+ '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
+ document.baseURI;
console.log('The keycloak auth has been initialized');
observer.next('Succeeded in initiating the keycloak client');
observer.complete();
})
.error(() => {
console.log('The keycloak client could not be initiated');
observer.error('Failed to initiate the keycloak client');
});
});
}
The whole source code is available on GitHub
UPDATE: Following an answer below, I also tried to use a then() and a catch() keywords but the error remained the exact same:
keycloakAuth.init({ 'onLoad': 'check-sso' })
.then(() => {
KeycloakClientService.auth.loggedIn = true;
KeycloakClientService.auth.authz = keycloakAuth;
KeycloakClientService.auth.logoutUrl = environment.KEYCLOAK_URL
+ '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
+ document.baseURI;
console.log('The keycloak auth has been initialized');
observer.next('Succeeded in initiating the keycloak client');
observer.complete();
})
.catch(() => {
console.log('The keycloak client could not be initiated');
observer.error('Failed to initiate the keycloak client');
});
This is a wild guess, but maybe it's a conflict with Angular's zones. Since this is a security library it might not like that Angular has replaced core functions with proxies. For example, NgZone modifies window.setTimeout and the HTTP methods.
So you could try running this code outside of zones. The only problem here is that you're using a static function, and will have to make this an injectable service so that you can access NgZone
#Injectable()
export class KeycloakClientService {
public constructor(private zone: NgZone) {
}
public init(): Promise<any> {
KeycloakClientService.auth.loggedIn = false;
return new Promise((resolve, reject) => {
this.zone.runOutsideAngular(() => {
const keycloakConfig = {
url: environment.KEYCLOAK_URL,
realm: environment.KEYCLOAK_REALM,
clientId: environment.KEYCLOAK_CLIENTID,
'ssl-required': 'external',
'public-client': true
};
const keycloakAuth: any = new Keycloak(keycloakConfig);
keycloakAuth.init({onLoad: 'check-sso'})
.success(() => {
KeycloakClientService.auth.loggedIn = true;
KeycloakClientService.auth.authz = keycloakAuth;
KeycloakClientService.auth.logoutUrl = environment.KEYCLOAK_URL
+ '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
+ document.baseURI;
console.log('=======>> The keycloak client has been initiated successfully');
resolve('Succeeded in initiating the keycloak client');
})
.error(() => {
reject('Failed to initiate the keycloak client');
});
});
}
}
}
The change here is to use zone.runOutsideAngular
If you remove the success block, where do you run your logic within success?
I read some of their source code, I think this is why success causes the problem:
Within keycloak.js, there is a function createNativePromise():
function createNativePromise() {
var p = {
setSuccess: function(result) {
p.success = true;
p.resolve(result);
},
setError: function(result) {
p.success = false;
p.reject(result);
}
};
p.promise = new Promise(function(resolve, reject) {
p.resolve = resolve;
p.reject = reject;
});
p.promise.success = function(callback) {
p.promise.then(callback);
return p.promise;
}
p.promise.error = function(callback) {
p.promise.catch(callback);
return p.promise;
}
return p;
}
And it's used this way(simplified code):
function refreshToken() {
var promise = createNativePromise();
...
if (refreshTokenFailed) {
promise.setError(true);
}
...
return promise.promise;
}
The problem is, promise.setError() calls promise.reject(result), so the promise is rejected, it's expecting a catch.
But within promise.success, there is a promise.promise.then(callback);, and nobody is catching this promise.
This is why you get the Uncaught (in promise): [object Undefined], and in my case, i always get Uncaught (in promise): true.
Solution:
Notice that promise.promise is a real Promise, so we can use then and catch instead of success and error.
The drawback is, the typescript type will be wrong.
We have observed a similar error about the promise object undefined. The situation was our local application was working fine with the local keycloak standalone server but faced this error when the local application trying to connect with a keycloak server hosted on the ABC server (ABC is used as a reference here to give any arbitrary name).
This issue was resolved when we hosted the application and the keycloak server both on the ABC server.
It looks like there are some time sync issues in different machines due to which the promise object is not returned.
Related
I have a Google Cloud Function which I am calling from my RN app but it is returning
[Error: Internal]
I have set the permission to Unauthenticated users so anyone can call it - for testing purposes only. When I set to Authenticated users permission, it throws another error [Error: Unauthenticated] eventhough I am authenticated and I can get the currentUser id in my app.
Tried searching for this error but it didnt send me to any possible solutions so decided to post here and hopefully recieve responses that will help me fix it.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUser = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
//Checking that the user calling the Cloud Function is authenticated
if (!context.auth) {
throw new UnauthenticatedError('The user is not authenticated. Only authenticated Admin users can create new users.');
}
const newUser = {
email: data.email,
emailVerified: false,
password: data.password,
disabled: false
}
const role = data.role;
const userRecord = await admin
.auth()
.createUser(newUser);
const userId = userRecord.uid;
const claims = {};
claims[role] = true;
await admin.auth().setCustomUserClaims(userId, claims);
return { result: 'The new user has been successfully created.' };
} catch (error) {
if (error.type === 'UnauthenticatedError') {
throw new functions.https.HttpsError('unauthenticated', error.message);
} else if (error.type === 'NotAnAdminError' || error.type === 'InvalidRoleError') {
throw new functions.https.HttpsError('failed-precondition', error.message);
} else {
throw new functions.https.HttpsError('internal', error.message);
}
}
});
in my RN app I am calling it like this:
var user = {
role: role
}
const defaultApp = firebase.app();
const functionsForRegion = defaultApp.functions('europe-west1');
const createUser = await functionsForRegion.httpsCallable('createUser');
createUser(user)
.then((resp) => {
//Display success
});
console.log(resp.data.result);
})
.catch((error) => {
console.log("Error on register patient: ", error)
});
I think the way I am calling it in my RN app is correct because I have tested it with a testFunction and I returned a simple string. So, I believe the problem is somewhere in the function itself.
EDIT: I just tested by simply calling the function and returning the context and it always returns Internal error:
exports.registerNewPatient = functions.region('europe-west3').https.onCall((data, context) => {
return context; //this is returned as INTERNAL error.
}
I just cant get to understand whats going on here, why does it return Internal error when I am authenticated as a user and it should return the authenticated user data, isn't that right?
Try some console.log(context) ; console.log(data) statements in your registerNewPatient function and take a look at the logs. What do they say?
Some other things to consider might include that in your client code you use europe-west1 while your function code has europe-west3. Try to have those line up and see if it works? From my experience, if a specified function isn't found to exist, the client receives an INTERNAL error.
I am trying to add a custom header on all network requests going from application and I am trying to do this via service worker fetch.
The content of header needs to come from app(client), so I need to wait for a response from client before responding to any event.
Below is my attempt to achieve this
Here is my fetch listener code
function send_message_to_client(client, msg){
return new Promise(function(resolve, reject){
var msg_chan = new MessageChannel();
msg_chan.port1.onmessage = function(event){
if(event.data.error){
reject(event.data.error);
}else{
resolve(event.data);
}
};
client.postMessage("SW Says: '"+msg+"'", [msg_chan.port2]);
});
}
self.addEventListener('fetch', function (event) {
event.waitUntil(async function () {
const client = await clients.get(event.clientId);
send_message_to_client(client, "Pass Transaction Details")
.then(function (m) {
var req = new Request(event.request.url, {
method: event.request.method,
headers: event.request.headers,
mode: 'same-origin',
credentials: event.request.credentials,
redirect: 'manual'
});
var res_obj = JSON.parse(m);
req.headers.append('MY_CUSTOM_HEADER', res_obj.hdr_val);
return event.respondWith(fetch(req));
})
.catch(function (error) {
console.log("Error after event.respondWith call");
console.log(error);
});
}());
});
and here is how I registered this worker and its message listener
navigator.serviceWorker.register('/my-sw.js', {scope: '/'})
.then(function(reg) {
navigator.serviceWorker.onmessage = function (e) {
var msg_reply = {
"message" : "Replying to SW request",
};
msg_reply.hdr_val = sessionStorage.getItem('__data_val');
console.log("Replying with "+ JSON.stringify(msg_reply));
e.ports[0].postMessage(JSON.stringify(msg_reply));
};
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
but apparently its shooting 2 requests, 1 original request and 1 with modified headers.
Any idea what am I doing wrong ? I am a newbie in javascript so pardon me if there is some stupid mistake.
From service worker debug console, I found that its going in catch block right after event.respondWith() call, so something wrong there probably ?
You must call FetchEvent.respondWith() synchronously in your fetch handler. In your code you are calling waitUntil() synchronously, but calling respondWith() later. By not calling respondWith() before returning from the fetch handler you are telling the browser to continue on with its normal networking path without interception.
I think you want something like this:
self.addEventListener('fetch', function (event) {
// call respondWith() here
event.respondWith(async function () {
const client = await clients.get(event.clientId);
send_message_to_client(client, "Pass Transaction Details")
.then(function (m) {
var req = new Request(event.request.url, {
method: event.request.method,
headers: event.request.headers,
mode: 'same-origin',
credentials: event.request.credentials,
redirect: 'manual'
});
var res_obj = JSON.parse(m);
req.headers.append('MY_CUSTOM_HEADER', res_obj.hdr_val);
// just return the response back to the respondWith() here
return fetch(req);
})
// You probably want to add a .catch() to return some reasonable fallback
// response.
Platform:
I have an api in sails.js and a frontend in react. The calls between front and back end are being made with fetch api.
More information:
In the course of some api endpoints I have to execute an external file, at this point I am using the execfile() function of node.js, and I have to wait for it to be executed to respond to the frontend.
What is the problem?
If the file is executed in a short time, for example less than 1 minute everything runs well and the behavior occurs as expected on both sides, but if (in this case) the file takes more than 1 minute to execute, there is something to trigger a second call to api (I do not know where this is being called, but I tested the same endpoint with postman and I did not have this problem so I suspect the react / fetch-api) and the api call with the same data from the first call is redone. This causes the file to run twice.
Something that is even stranger is that if you have the DevTools Network inspector turned on this second call does not appear, but nothing in the documentation of sailjs points to this behavior.
Example of an endpoint in sails.js:
/**
* FooController
*/
const execFile = require("child_process").execFile;
module.exports = {
foo: async (req, res) => {
let result = await module.exports._executeScript(req.body).catch(() => {
res.status(500).json({ error: "something has occurred" });
});
res.json(result);
},
_executeScript: body => {
return new Promise((resolve, reject) => {
let args = [process.cwd() + "/scripts/" + "myExternalFile.js", body];
let elem = await module.exports
._execScript(args)
.catch(err => reject(err));
resolve(elem);
});
},
_execScript: args => {
return new Promise((resolve, reject) => {
try {
execFile("node", args, { timeout: 150000 }, (error, stdout, stderr) => {
if (error || (stderr != null && stderr !== "")) {
console.error(error);
} else {
console.log(stdout);
}
let output = { stdout: stdout, stderr: stderr };
resolve(output);
});
} catch (err) {
reject(err);
}
});
}
};
Example of component react with fetch call:
import React from "react";
import { notification } from "antd";
import "./../App.css";
import Oauth from "./../helper/Oauth";
import Config from "./../config.json";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
syncInAction: false,
data: null
};
}
componentDidMount() {
this.handleSync();
}
async handleSync() {
let response = await fetch(Config.apiLink + "/foo/foo", {
method: "POST",
mode: "cors",
headers: {
Authorization: Oauth.isLoggedIn(),
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(this.state.myData)
}).catch(err => {
notification.error({
key: "catch-ApiFail",
message: "Erro"
});
});
let json = await response.json();
this.setState({
syncInAction: false,
data: json
});
}
render() {
return <div>{this.state.data}</div>;
}
}
export default App;
What is my expected goal / behavior:
It does not matter if the call takes 1 minute or 10 hours, the file can only be called once and when it finishes, then yes, it can return to the frontend.
Note that the examples do not have the original code and have not been tested. Is a simplified version of the code to explain the behavior
I ended up solving the problem, apparently nodejs has a default timing of 2 minutes on the server, and can be rewritten to miss this timout or increase it.
This was just adding a line of code at the beginning of the foo() endpoint and the problem was solved.
The behavior of redoing the call is that it is not documented, and it is strange not to have this behavior when using the postman, but here is the solution for whoever needs it.
Final result:
foo: async (req, res) => {
req.setTimeout(0);
let result = await module.exports._executeScript(req.body).catch(() => {
res.status(500).json({ error: "something has occurred" });
});
res.json(result);
};
I am using the imap-simple NPM package to check emails, and I am having trouble getting the insert to work properly.
I have already read through this page: https://guide.meteor.com/using-npm-packages.html#async-callbacks - and I have tried the suggestions but none of them are working!
I've also simplified the code a bit just to try to get it working, but still have no luck.
The problem should be very easy to reproduce - meteor npm install imap-simple, throw the above code on the server, add some email credentials, and call the method.
Here is my code:
var imaps = require('imap-simple');
var config = {
imap: {
user: '<removed>',
password: '<removed>',
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
Meteor.methods({
api_connectEmail: function () {
console.log('Received call to connect email');
imaps.connect(config).then(function (connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = [
'UNSEEN'
];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: true
};
return connection.search(searchCriteria, fetchOptions).then(function (results) {
results.map(function (res) {
var subject = res.parts.filter(function (part) {return part.which === 'HEADER';})[0].body.subject[0];
console.log("Subject: " + subject);
// insert
var attributes = {
subject: subject
};
console.log("Attempting to insert to collection...");
var newData = TempEmailCollection.insert(attributes);
console.log("New Database Entry ID: " + newData);
});
});
});
})
}
});
The console.log with the subject is working. The insert is not working. No error, no console.log post insert, nothing.
I've tried both strategies recommended in the guide, neither work.
The problem is that you are calling a Meteor function inside asynchronously called Promise handlers.
However, all Meteor functions that are called on the server have to run in a fiber.
Meteor actually throws an error in this case but you are ignoring it because you haven't specified catch functions for the Promises.
Consider the following simplified example (it just connects to the server and tries to insert a new document):
import { Meteor } from 'meteor/meteor';
import imaps from 'imap-simple';
const Storage = new Mongo.Collection('storage');
const config = {
imap: {
…
}
};
Meteor.methods({
connect() {
console.log('Method called');
imaps.connect(config).then(function(connection) {
console.log('Connected');
Storage.insert({
value: 'success'
});
console.log('Document inserted');
})
.catch(function(err) {
console.error(err);
});
}
});
The following message will arrive in the catch function:
[Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.]
You could do something like this to wrap the insert call:
Meteor.methods({
connect() {
console.log('Method called');
const insert = Meteor.bindEnvironment(function() {
Storage.insert({
value: 'success'
});
});
imaps.connect(config).then(function(connection) {
console.log('Connected');
insert();
console.log('Document inserted');
})
.catch(function(err) {
console.error(err);
});
}
});
Then the document will be inserted as expected.
I have a piece of middleware that checks a users authentication based on a passed parameter. The middleware uses a model that implements promises to find and return the user to be set into the request params.
The problem is, when running tests, if an assertion fails the test times out, presumably because the exception thrown by the failing assertion is not able to be handled by Mocha.
I'm doing the assertions inside of the next() function - When testing that the key was not provided, it works correctly, I assume because it's not being run in the context of the promise.
# Authenticator
var Authentication = function(model) {
this.model = model;
};
Authentication.prototype.resolve = function(customerKey) {
return this.model.authenticate(customerKey);
};
module.exports = function(model) {
return function(req, res, next) {
if (!req.query.hasOwnProperty('customerKey')) {
throw new Error('There was no auth key provided');
}
var auth = new Authentication(model);
auth.resolve(req.query.customerKey)
.then(function(customer) {
if (!customer) {
throw new Error('There was no customer found with the supplied auth key');
}
req.params.auth = customer;
})
.done(next, next);
};
};
# Test
var should = require('chai').should(),
authentication = require('src/api/middleware/authentication'),
model = require('src/models/customer'),
auth = authentication(model);
describe('middleware/authentication', function() {
it('should set the user to the request if the customerKey is valid', function(done) {
var request = {
query: {
customerKey: 'thisIsValid'
},
params: {}
};
var response = function() {
// This is a no-op
};
var next = function(response) {
response.should.be.instanceOf(String); // If this assertion fails, the test times out and doesn't work
response.should.have.property('name');
response.name.should.be.a('string');
done();
};
// Actually calls the auth
auth(request, response, next);
});
});