Loopback: Event resetPasswordRequest not called after reset - loopbackjs

I am trying to tap into the password reset process on Loopback, so I can send an e-mail with the instructions to the user. I created a custom model called 'user' that extends 'User'. Then I added "User.on('resetPasswordRequest'...", but it never gets executed. Please check the console.log on the user.js
model-config.json
{
...
"user": {
"dataSource": "mysqlDs",
"public": true,
"options": {
"emailVerificationRequired": false
}
},
...
}
user.json
{
"name": "user",
"base": "User",
"idInjection": true,
"properties": {},
"validations": [],
"relations": {},
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"accessType": "READ",
"permission": "ALLOW"
}
],
"methods": []
}
user.js
module.exports = function(User) {
console.log('It prints this log here.');
User.on('resetPasswordRequest', function(info) {
console.log('But it does not print this log here ever.');
var url = 'http://www.example.com/reset-password';
var html = 'Click here';
loopback.Email.send({
to: info.email,
from: 'mail#example.com',
subject: 'My Subject',
html: html
}, function() {
console.log('> sending password reset email to:', info.email);
if (err) return console.log('> error sending password reset email');
});
});
};

Ok, so #IvanSschwarz was right. Thank you so much! My initial code was correct, but it was missing a small change on the model-config.json: I needed to hide the User model for some reason. So I also took the opportunity and changed my model name from "user" to "member", to follow the loopback good practices guide lines.
...
"User": {
"dataSource": "mysqlDs",
"public": false
},
"member": {
"dataSource": "mysqlDs",
"public": true,
"options": {
"emailVerificationRequired": true
}
},
....
I believe that the other suggestions could potentially work as well, but I wanted to extend the User model through JSON and also leverage the built-in remote method Reset Password from the Users model. So thank you guys!

The solution is explained here:
http://loopback.io/doc/en/lb3/Extending-built-in-models.html#setting-up-a-custom-model
So you will need:
User.setup = function() {
var User = this;
// since setup is called for every extended model
// the extended model will also have the event listener
User.on('resetPasswordRequest', function() {
///Do your stuff here
});
}

Don't you simply miss to define a remote method in order to call your code?
This is what I would do:
user.js
module.exports = function(User) {
console.log('It prints this log here.');
User.resetPasswordRequest = function (info, cb) {
console.log('But it does not print this log here ever.');
var url = 'http://www.example.com/reset-password';
var html = 'Click here';
loopback.Email.send({
to: info.email,
from: 'mail#example.com',
subject: 'My Subject',
html: html
}, function() {
console.log('> sending password reset email to:', info.email);
if (err) {
cb(err, false);
return console.log('> error sending password reset email');
}
cb(err, true):
});
});
User.remoteMethod('resetPasswordRequest', {
description: 'Send a mail to an User to permit him to reset his password',
accepts: {arg: 'info', type: 'object', required: true},
returns: {arg: 'mailSend', type: 'boolean'},
http: {verb: 'post'}
});
};
Then, using the Explorer or calling the API via REST while posting a your info json like:
{
accessToken: xxx,
email: xxx
}
And don't forget to update your ACLs in user.json so you can call the method from remote:
{
"name": "user",
"base": "User",
"idInjection": true,
"properties": {},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "resetPasswordRequest"
}
],
"methods": []
}

Related

Google action request for grant_type=refresh_token does not update conversation object with new access token

I'm developing a google action which is configured for account linking using Linking Type: "OAuth" / "Authorization code". The OAuth2 authorization server functions are hosted in the same place as the linked business application server, so I have full visibility of the traffic in both regards.
The initial linking process works perfectly every time; however, a problem arises when the original access token expires, and a new one is requested grant_type=refresh_token. Included below are the sanitized log contents from our server's execution logic generated by invocation of the google action using Actions Console "Test" simulator, after the original access token has expired. As expected, the google action first requests a new access token by calling the Token Url with grant_type=refresh_token. Our server then returns content to the caller
shown below under SmartAssistOAuthToken: result= as
{"expires_in":600,"token_type":"Bearer","access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.5XRFvkrBr7PCfyyqSMW1nNJBI0EceQgFrMA-6I04sQA"}
Having received the new token, the Google action proceeds to the main intent / webhook handler which contains a REST request back to the same server to retrieve some information about the current user's account.
This is shown below as: >****> 2021/07/15 12:16:18 (-05:00) - /cgi-bin/w2w.dll/sa_anon?cmd=login...
>********> 2021/07/15 12:16:15 (-05:00) - /cgi-bin/w2w.dll/AoG_token?
----------------------------------------------------------------------------------------------------
SmartAssistOAuthToken: contentfields=
grant_type=refresh_token
refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.65URfGpCL4s9nZiUc2pvYRZydK6jKanI5DZAZm04BtM
client_id=xxxx
client_secret=xxxx
----------------------------------------------------------------------------------------------------
SmartAssistOAuthToken: result= (...using 600 to speed things up a bit)
{"expires_in":600,"token_type":"Bearer","access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.5XRFvkrBr7PCfyyqSMW1nNJBI0EceQgFrMA-6I04sQA"}
----------------------------------------------------------------------------------------------------
>********> 2021/07/15 12:16:18 (-05:00) - /cgi-bin/w2w.dll/sa_anon?cmd=login&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.FfUGlS71IaIU3LdkiJtUKfBBZwjSaN5D0s1ECpzxop4
----------------------------------------------------------------------------------------------------
SmartAssistLogin: QueryFields=
cmd=login
access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.FfUGlS71IaIU3LdkiJtUKfBBZwjSaN5D0s1ECpzxop4
----------------------------------------------------------------------------------------------------
ValidAccessTokenInfo: AccessToken's JWT has expired.
----------------------------------------------------------------------------------------------------
SmartAssistError: Error={"error_uri":"https:xxxx","error_description":"Invalid authorization credential.","error":"invalid_request"}
At the end of the "login" REST request is the access token which I read from conv.session.params.bearerToken, and then add to the url.
/cgi-bin/w2w.dll/sa_anon?cmd=login&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.FfUGlS71IaIU3LdkiJtUKfBBZwjSaN5D0s1ECpzxop4
However, the value provided by conv.session.params.bearerToken (eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.FfUGlS71IaIU3LdkiJtUKfBBZwjSaN5D0s1ECpzxop4) is not the new one returned to the refresh token request (eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.5XRFvkrBr7PCfyyqSMW1nNJBI0EceQgFrMA-6I04sQA). As such, my server returns an error for the request because the original token is no longer valid / expired.
The entire (sanitized) incoming conv object is as follows, wherein all references to bearerToken are the same recently expired token:
{
"overwrite": false,
"digested": false,
"request": {
"handler": {
"name": "main"
},
"intent": {
"name": "actions.intent.MAIN",
"params": {},
"query": "Talk to work assistant"
},
"scene": {
"name": "Main",
"slotFillingStatus": "UNSPECIFIED",
"slots": {}
},
"session": {
"id": "ABwppHFsSDza7b0R74-kOOBCudqHWhrjIMma8mnaLjTDFg55O68MIjBIPRgaSEyAH31RPOJGG9VhrIl283LWY6xXZsw",
"params": {},
"typeOverrides": [],
"languageCode": ""
},
"user": {
"locale": "en-US",
"params": {
"AccountLinkingSlot": "LINKED",
"bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.FfUGlS71IaIU3LdkiJtUKfBBZwjSaN5D0s1ECpzxop4",
},
"accountLinkingStatus": "LINKED",
"verificationStatus": "VERIFIED",
"packageEntitlements": [],
"gaiamint": "",
"permissions": [],
"lastSeenTime": "2021-07-15T16:15:48Z"
},
"home": {
"params": {}
},
"device": {
"capabilities": ["SPEECH", "RICH_RESPONSE", "LONG_FORM_AUDIO"],
"timeZone": {
"id": "America/Chicago",
"version": ""
}
}
},
"headers": {
"host": "us-central1-work-assistant-b9910.cloudfunctions.net",
"user-agent": "Google-ActionsOnGoogle/1.0",
"transfer-encoding": "chunked",
"accept-encoding": "gzip, deflate, br",
"content-type": "application/json;charset=UTF-8",
"forwarded": "for=\"66.249.83.57\";proto=https",
"function-execution-id": "zn46t5q3mkfr",
"google-actions-api-version": "3",
"google-assistant-signature": "xxxx",
"traceparent": "00-6b5c4d0aa670f39009910ec1f7e908ee-2abd7ee7886b286d-00",
"x-appengine-country": "ZZ",
"x-appengine-default-version-hostname": "d1c092144ed53556ap-tp.appspot.com",
"x-appengine-https": "on",
"x-appengine-request-log-id": "60f06d6200ff0c6741e353b4fb0001737e6431633039323134346564353335353661702d7470000133343061346661353033613135323331626363326539303833646463386639313a310001010c",
"x-appengine-timeout-ms": "599998",
"x-appengine-user-ip": "66.249.83.57",
"x-cloud-trace-context": "6b5c4d0aa670f39009910ec1f7e908ee/3079757253082556525",
"x-forwarded-for": "66.249.83.57",
"x-forwarded-proto": "https",
"connection": "close"
},
"handler": {
"name": "main"
},
"intent": {
"name": "actions.intent.MAIN",
"query": "Talk to work assistant",
"params": {}
},
"scene": {
"name": "Main",
"slotFillingStatus": "UNSPECIFIED",
"slots": {},
"next": {}
},
"session": {
"id": "ABwppHFsSDza7b0R74-kOOBCudqHWhrjIMma8mnaLjTDFg55O68MIjBIPRgaSEyAH31RPOJGG9VhrIl283LWY6xXZsw",
"params": {
"AccountLinkingSlot": "LINKED",
"bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.FfUGlS71IaIU3LdkiJtUKfBBZwjSaN5D0s1ECpzxop4",
},
"typeOverrides": [],
"languageCode": ""
},
"user": {
"locale": "en-US",
"params": {
"AccountLinkingSlot": "LINKED",
"bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.FfUGlS71IaIU3LdkiJtUKfBBZwjSaN5D0s1ECpzxop4",
},
"accountLinkingStatus": "LINKED",
"verificationStatus": "VERIFIED",
"packageEntitlements": [],
"gaiamint": "",
"permissions": [],
"lastSeenTime": "2021-07-15T16:15:48Z"
},
"device": {
"capabilities": ["SPEECH", "RICH_RESPONSE", "LONG_FORM_AUDIO"],
"currentLocation": {},
"timeZone": {
"id": "America/Chicago",
"version": ""
}
},
"home": {
"params": {}
},
"expected": {},
"context": {},
"prompt": {
"override": false
},
"_internal": {
"promptSet": false,
"orig": {
"scene": {
"name": "Main",
"slotFillingStatus": "UNSPECIFIED",
"slots": {},
"next": {}
},
"session": {
"id": "ABwppHFsSDza7b0R74-kOOBCudqHWhrjIMma8mnaLjTDFg55O68MIjBIPRgaSEyAH31RPOJGG9VhrIl283LWY6xXZsw",
"params": {},
"typeOverrides": [],
"languageCode": ""
},
"user": {
"locale": "en-US",
"params": {
"AccountLinkingSlot": "LINKED",
"bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx.FfUGlS71IaIU3LdkiJtUKfBBZwjSaN5D0s1ECpzxop4",
},
"accountLinkingStatus": "LINKED",
"verificationStatus": "VERIFIED",
"packageEntitlements": [],
"gaiamint": "",
"permissions": [],
"lastSeenTime": "2021-07-15T16:15:48Z"
},
"home": {
"params": {}
}
}
}
}
The relevant google action webhook code looks something like this:
const {conversation,
Suggestion,
Simple,
Card,
Image
} = require('#assistant/conversation');
const functions = require('firebase-functions');
const app = conversation();
...
const getAccessToken = (conv) => {
return conv.session.params.bearerToken;
};
...
app.handle('main', async conv => {
...
console.log("main: >>>>>>>>>>>>>> conv=",JSON.stringify(conv));
...
if (conv.user.verificationStatus !== 'VERIFIED') {
conv.add('Sorry, your account is not verified yet so you cannot be here.');
return;
}
...
await getRemoteData(`${getFullredirectpath()}sa_anon?cmd=login&access_token=${getAccessToken(conv)}`)
.then((response) => {
(success handling...)
})
.catch((err) => {
(Error handling...)
return;
});
...
}
This problem doesn't happen every time a token is refreshed, but it does happen frequently, and seemingly more so, the longer the time between sessions. I can almost always count on it to happen overnight.

How to test a GET and POST call from inside of Lambda

I am learning Lambda, have created a Cloud Watch Metrics which triggers Lambda every one minute. At present, my Lambda function is executing properly. But I want to make a fake GET and POST call from inside of Lambda. Something like below:
var https = require('https');
exports.handler = (event, context, callback) => {
var params = {
host: "example.com",
path: "/api/v1/yourmethod"
};
var req = https.request(params, function(res) {
let data = '';
console.log('STATUS: ' + res.statusCode);
res.setEncoding('utf8');
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
console.log("DONE");
console.log(JSON.parse(data));
});
});
req.end();
};
Question - Since I don't have any GET and POST endpoint/Code hosted in AWS is there any way I can test whether my GET call and POST call from inside of Lambda is working fine? Is there any way in AWS to put together a fake endpoint or something in AWS and make REST call to it for testing purposes?
You can use JSONPlaceholder's APIs for testing calls to external APIs,
They have released Fake Online REST APIs for Testing and Prototyping
You can test calling different http method calls like GET, PUT, POST, DELETE
https://jsonplaceholder.typicode.com/
In case you are using Lambda proxy integration Event will contents a lot of additional information about request: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
{
"message": "Hello me!",
"input": {
"resource": "/{proxy+}",
"path": "/hello/world",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"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": "US",
"Content-Type": "application/json",
"headerName": "headerValue",
"Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com",
"Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f",
"User-Agent": "PostmanRuntime/2.4.5",
"Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==",
"X-Forwarded-For": "54.240.196.186, 54.182.214.83",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"multiValueHeaders":{
'Accept':[
"*/*"
],
'Accept-Encoding':[
"gzip, deflate"
],
'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':[
"US"
],
'':[
""
],
'Content-Type':[
"application/json"
],
'headerName':[
"headerValue"
],
'Host':[
"gy415nuibc.execute-api.us-east-1.amazonaws.com"
],
'Postman-Token':[
"9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f"
],
'User-Agent':[
"PostmanRuntime/2.4.5"
],
'Via':[
"1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)"
],
'X-Amz-Cf-Id':[
"pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A=="
],
'X-Forwarded-For':[
"54.240.196.186, 54.182.214.83"
],
'X-Forwarded-Port':[
"443"
],
'X-Forwarded-Proto':[
"https"
]
},
"queryStringParameters": {
"name": "me",
"multivalueName": "me"
},
"multiValueQueryStringParameters":{
"name":[
"me"
],
"multivalueName":[
"you",
"me"
]
},
"pathParameters": {
"proxy": "hello/world"
},
"stageVariables": {
"stageVariableName": "stageVariableValue"
},
"requestContext": {
"accountId": "12345678912",
"resourceId": "roq9wj",
"stage": "testStage",
"requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33",
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"apiKey": null,
"sourceIp": "192.168.196.186",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "PostmanRuntime/2.4.5",
"user": null
},
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"apiId": "gy415nuibc"
},
"body": "{\r\n\t\"a\": 1\r\n}",
"isBase64Encoded": false
}
}

&owner for posting new model doesn't work

Using this ACL for models, I want to prevent creating new models that have owner (userId) for those access-tokens that lead to other users, rather than the actual owner. I don't want userId 4 post a new model, and set that model's owners to 5, another userId. But this doesn't work right now, since there is no model on database to check, so what should I do? This worked for deletion last time I checked. But I need for posting...
I think this will do it.
server/boot/01-add-user.js
module.exports = (server) => {
// Before each create request, assign the userId property to the userId of the accessToken
const addUser = async (ctx) => {
function assignUserId(o) {
o.userId = ctx.req.accessToken.userId;
}
// You can post arrays of objects to loopback
if (Array.isArray(ctx.req.body)){
ctx.req.body.forEach(assignUserId);
} else {
assignUserId(ctx.req.body);
}
};
server.models.MY_MODEL.beforeRemote('create', addUser);
}
For your ACLs try this
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
}
],

loopback remote method does not accept parameter or give error

I have generated remote method using
slc loopback:remote-method
I tried to access this method from explorer and it give status 400 with message "phoneNumber is a required arg"
When I tried with postman it gives status 500 with message Internal sever error. I tried sending post data as form, x-www-form-urlencoded encoded, and row, it gives same result.
Server side error:
Unhandled error for request POST /otp/getOTP: TypeError: Cannot read property 'modelName' of null
at convertToBasicRemotingType (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:390:16)
at /Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:544:20
at Array.filter (native)
at Function.SharedMethod.toResult (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:534:21)
at callback (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:249:31)
at Function.Onetimepassword.getOTP (/Users/manish/Documents/workspace-node/carbuk-services/common/models/one-time-password.js:14:4)
at SharedMethod.invoke (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:263:25)
at HttpContext.invoke (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/http-context.js:387:12)
at phaseInvoke (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/remote-objects.js:644:9)
at runHandler (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/loopback-phase/lib/phase.js:135:5)
at iterate (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:146:13)
at Object.async.eachSeries (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:162:9)
at runHandlers (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/loopback-phase/lib/phase.js:144:13)
at iterate (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:146:13)
at /Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:157:25
at /Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:154:25
model json look like this
{
"name": "OneTimePassword",
"plural": "otp",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"phoneNumber": {
"type": "number",
"required": true
},
"otpNumber": {
"type": "number",
"required": true
},
"resendCounter": {
"type": "number",
"default": "0"
},
"createdDate": {
"type": "date"
} }, "validations": [], "relations": {}, "acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "deleteById"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "getOTP"
} ], "methods": {
"getOTP": {
"accepts": [
{
"arg": "phoneNumber",
"type": "number",
"required": true,
"description": "phone number",
"http": {
"source": "form"
}
}
],
"returns": [
{
"arg": "oneTimePassword",
"type": null,
"root": true,
"description": "otp"
}
],
"description": "generate otp and send sms",
"http": [
{
"path": "/getOTP",
"verb": "post"
}
]
} } }
model js:
module.exports = function(Onetimepassword) {
/**
* generate otp and send sms
* #param {number} phoneNumber phone number
* #param {Function(Error, )} callback
*/
Onetimepassword.getOTP = function(phoneNumber, callback) {
var oneTimePassword = {};
oneTimePassword.phoneNumber = phoneNumber;
// TODO
// logic will come here
//
callback(null, oneTimePassword);
};
};
Am I missing something?
You have a typo.
in line oneTimePassword.phoneNumber = phoneNumber; you need to set to optNumber
I found the solution.
In model.json, Remote method's return type is null. I changed this to object type "OneTimePassword"
But still I am not able to hit the remote method from explorer. There is a bug issue#440.
Using postman, request work fine

How to authenicate using static role in Loopback ACL

Having issues getting authentication working using static roles access control within models. Things seem to be working when using ACL's with principal types $authenticated and $everyone. So access controls are in place and functioning as expected when logged in and logged out. As ACL's are moved over to static roles authentication fails and a 401 is returned. Loopback built in models for for roles, role-mapping, and user are being used. I've tried using ROLE and USER as principalTypes.
Creating User, Role, and principal with RoleMapping:
User.create({
username: 'admin',
email: 'admin#admin.com',
password: 'password',
active: true
},
function (err, user) {
Role.create({
name: 'Admin'
},
function (err, role) {
if (err) throw err;
console.log('Created role:', role);
//make user an admin
role.principals.create({
principalType: RoleMapping.USER,
//principalType: RoleMapping.ROLE,
principalId: user.id,
active: true
},
function (err, principal) {
if (err) throw err;
console.log('Principal:', principal);
});
});
});
Customer Model:
"name": "Customer",
"base": "PersistedModel",
"strict": false,
"idInjection": false,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
},
"description": {
"type": "string"
},
"active": {
"type": "boolean"
}
},
"validations": [],
"relations": {
"products": {
.........
},
"users": {
.........
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
WORKS AS EXPECTED
"accessType": "*",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
RETURNS 401 AFTER LOGGING IN AS USER ASSIGNED TO ROLE
"accessType": "*",
"principalType": "ROLE",
"principalId": "Admin",
"permission": "ALLOW"
}
],
"methods": []
User record created:
"_id" : ObjectId("55b7c34d6033a33758038c3b"),
"username" : "admin",
"password" : ....,
"email" : "admin#admin.com",
"active" : true
Role record:
"_id" : ObjectId("55b7c34d6033a33758038c3e"),
"name" : "Admin",
"created" : ISODate("2015-07-28T18:00:45.336Z"),
"modified" : ISODate("2015-07-28T18:00:45.336Z")
RoleMapping Record:
"_id" : ObjectId("55b7c34d6033a33758038c41"),
"principalType" : "USER",
"principalId" : "55b7c34d6033a33758038c3b",
"roleId" : ObjectId("55b7c34d6033a33758038c3e"),
"active" : true
Thanks before hand for any help!
When you define the user in principalId, try try instead:
principalId: user[0].id,
Please note at:
RoleMapping.principalType: USER (mean user, not ROLE)
RoleMapping.principalId: USER_ID (because define RoleMapping.principalType is USER)
acls: [
{ "principalType": "ROLE",
"principalId": "admin",
}
]
I have tested, it's working
Role
—————————
_id: ObjectId(5c70409a98103f1af6ee2b55)
name: “admin”,
description: “Only Admin can write”,
Role Mapping
——————————
_id: ObjectId(5c7040f998103f1af6ee2b57)
principalType: USER
principalId: 5c72b9ef79dcf14443c1aa3b
roleId: ObjectId(5c70409a98103f1af6ee2b55)
User
——————————
_id: ObjectId(5c72b9ef79dcf14443c1aa3b)
firstName: “”,
lastName: “”,
email:””,
Active:””,
emailVerified:
ACL
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}