I had implemented Parse Server(use parse-server-example) reset password via mailgun-adapter, the config like below:
var api = new ParseServer({
....
verifyUserEmails: true,
emailVerifyTokenValidityDuration: 2 * 60 * 60,
preventLoginWithUnverifiedEmail: false,
publicServerURL: 'http://my.domain.com',
appName: 'MyApp',
emailAdapter: {
module: 'parse-server-mailgun-adapter-template',
options: {
fromAddress: 'no-reply#mydomain.com',
domain: 'domain.mailgun',
apiKey: 'key-FromMailGun'
}
},
....
});
After send reset password via my App, I will receive the mail like:
Hi,
You requested a password reset for MyApp.
Click here to reset it:
http://my.domain.com/apps/appid/request_password_reset?token=tokenid&username=username
But, I click the link that attached in mail. the web display
Cannot GET /apps/appid/request_password_reset?token=token&username=username.
Therefore, I have to implement my app.get(/request_password_reset) function by myself? or can use Parse Server default html and forms?
I have read through your index.js and i think the issue is being your publicServerURL. Make sure you are using the same URL as your serverURL.
http://192.168.200.45:1111/parse
Related
I am working on a serverless project using node.js and AWS Lambda.
For auth, I am using AWS Cognito. (Frontend is a web-app in Vue.js on AWS Amplify).
I would like to write my own implementation of resetting a user's password who has forgotten their password.
Basically, the end-user fills up a form with their email. If email is in the system, I send them a reset link (which has a unique code I set in the DB).
I am aware of Cognito's Forgot Password flow and also a solution in which I can capture Cognito's "email sending" code and over-ride the email with my own template passing the code in the URL mentioned here.
I stumbled upon the adminSetUserPassword API which I was sure would work -- but no matter what I do, my lambda function does not get permissions to execute this operation.
This is my nodejs code:
import AWS from 'aws-sdk';
const COGNITO_POOL_ID = process.env.COGNITO_USERPOOL_ID;
const csp = new AWS.CognitoIdentityServiceProvider();
export async function resetUserPassword(username, newPassword) {
// Constructing request to send to Cognito
const params = {
Password: newPassword,
UserPoolId: COGNITO_POOL_ID,
Username: username,
Permanent: true,
};
await csp.adminSetUserPassword(params).promise();
return true;
}
This is my IAM permission for the lambda function (it is in serverless yml format):
CognitoResetPasswordIAM:
Effect: Allow
Action:
- cognito-idp:*
Resource:
- arn:aws:cognito-idp:us-east-1::*
(I will fine-tune the permissions once this works)
The following is the error message I am getting.
I am starting to feel that my approach to doing this is not the recommended way of doing things.
User: arn:aws:sts::[XXXXXXX]:assumed-role/[YYYYYYYYY]-us-east-1-lambdaRole/web-app-service-dev-resetPassword is not authorized to perform: cognito-idp:AdminSetUserPassword on resource: arn:aws:cognito-idp:us-east-1:[[XXXXXXX]]:userpool/us-east-1_ZZZZZZZZ
(Serverless has access to my AWS Access key with * permissions on * resources -- so I don't think I am missing any permissions there).
My questions:
Is this the recommended way of doing this?
Is it possible for me to configure permissions in a way that my lambda functions have the required permissions to perform this operation?
It turns out, you need to use the Amplify API and not the Cognito API.
This involves a couple of steps:
1. Configure your Cognito Amplify Service for Auth.
import Amplify, { Auth } from 'aws-amplify';
export function configureCognitoAuth() {
Amplify.configure({
Auth: {
region: process.env.COGNITO_REGION,
userPoolId: process.env.COGNITO_USERPOOL_ID,
mandatorySignIn: false,
userPoolWebClientId: process.env.COGNITO_CLIENT_ID,
authenticationFlowType: 'USER_PASSWORD_AUTH',
oauth: {
domain: process.env.COGNITO_APP_DOMAIN,
scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
responseType: 'code', // or 'token', note that REFRESH token will only be generated when the responseType is code
},
},
});
// You can get the current config object
Auth.configure();
}
2. Call the Auth.forgotPassword service to send the actual password here
import { Auth } from 'aws-amplify';
async function sendUserPasswordResetEmail(event) {
// Any validation checks, rate limits you want to check here, etc.
try {
configureCognitoAuth();
await Auth.forgotPassword(userId);
} catch (error) {
// An error occurred while sending the password reset email
}
}
3. Write a forgotPasswordEmailTrigger Cognito Hook
This replaces the default Cognito Reset password email with your own custom email.
This is also a lamdba method which you need to attach to the Cognito Custom Message trigger (from Cognito > General Settings > Triggers)
My code for this looks like so:
async function forgotPasswordEmailTrigger(event, context, callback) {
// Confirm it is a PreSignupTrigger
if (event.triggerSource === 'CustomMessage_ForgotPassword') {
const { userName } = event;
const passwordCode = event.request.codeParameter;
const resetUrl = `${BASE_URL}/password_reset/${userName}/${passwordCode}`;
let message = 'Your HTML email template goes here';
message = message
.replace(/{{passwordResetLink}}/g, resetUrl);
event.response.emailSubject = 'Email Subject here';
event.response.emailMessage = message;
}
// Return to Amazon Cognito
callback(null, event);
}
The event.request.codeParameter is where the code is returned from Cognito. I think there is a way to change this, but I didn't bother. I use the same code to verify in the next step.
4. Call the forgotPasswordSubmit method from the Amplify Auth service when a password reset request is sent to your backend
When the user clicks the URL, they come to the website and I pick up the code and the userID from the URL (from Step 3) and then verify the code + reset the password like so:
async function resetPassword(event) {
const { token, password, user_id } = event.body;
// Do your validations & checks
// Getting to here means everything is in order. Reset the password
try {
configureCognitoAuth(); // See step 1
await Auth.forgotPasswordSubmit(user_id, token, password);
} catch (error) {
// Error occurred while resetting the password
}
const result = {
result: true,
};
return {
statusCode: 200,
body: JSON.stringify(result),
};
}
Using the Bot Framework w/ Microsoft.Bot.Builder v4.6.3
Is it possible to have users sign in only once using the web-based authentication flow, doesn't matter if they sign in via tabs or via bot conversation? If they sign in via a link from a tab, I'd like to have the bot know about this.
I have tried the following for test, omitting any security checks:
All pages are with the following js files imported:
https://statics.teams.microsoft.com/sdk/v1.4.2/js/MicrosoftTeams.min.js
https://cdnjs.cloudflare.com/ajax/libs/oidc-client/1.9.1/oidc-client.min.js
On load, the tab page executes microsoftTeams.initialize();
Add a button to the tab page:
<button onclick="authenticate()">Authenticate</button>
The authenticate function contains the following:
function authenticate() {
microsoftTeams.authentication.authenticate({
url: window.location.origin + "/tabs/tabAuthStart",
width: 600,
height: 535,
successCallback: function (result) {
// The debug function just displays what's sent to it using document.write()
debug(result);
},
failureCallback: function (reason) {
debug(reason);
}
});
}
The tabAuthStart page contains the following script which is executed on page load:
microsoftTeams.initialize();
const mgr = new Oidc.UserManager({
userStore: new Oidc.WebStorageStateStore(),
authority: '<my-identity-server>',
client_id: '<my-id-srv-client>',
redirect_uri: window.location.origin + '/tabs/tabAuthCallback',
response_type: 'id_token token',
scope: '<my-requested-scopes>',
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: true,
filterProtocolClaims: true,
loadUserInfo: true
});
mgr.signinRedirect();
After a successful sign in at the identity provider, I'm redirected back to /tabs/tabAuthCallback
On load, the /tabs/tabAuthCallback executes the following code:
microsoftTeams.initialize();
var mgr = new Oidc.UserManager({ userStore: new Oidc.WebStorageStateStore(), loadUserInfo: true, filterProtocolClaims: true });
mgr.signinRedirectCallback().then(function (user) {
// I expected something involving a bot to happen after calling this
microsoftTeams.authentication.notifySuccess({
idToken: user.id_token,
accessToken: user.access_token,
tokenType: user.token_type,
expiresIn: user.expires_at
})
}).catch(function (err) {
microsoftTeams.authentication.notifyFailure("UnexpectedFailure: " + err);
});
The pop-up window is closed and the successCallback function from the tab is executed successfully with the user information that I have sent. However, the bot is not in any way notified about this (as far as I know). I have set a breakpoint in the bot controller action resolved by POST /api/messages but it's never hit.
Do I need to handle this manually? I.e. pass the user info to the back-end? But even if so, how do I know which Teams user to associate this user info (i.e. access token) to?
If this is possible to do in a reliable and secure way, would it also be possible in the opposite direction, i.e. having the user token available to the tab if they have already been authenticated from a bot conversation or a messaging extension? Is there a reliable way to identify a Teams user who's navigating tabs, in order to obtain their access token from the back-end, assuming the back-end already obtained them via the authentication mechanism?
According to Parse js guide:
The Parse.User obtained from Parse.User.current() will always be authenticated. If you need to check if a Parse.User is authenticated,
you can invoke the authenticated method. You do not need to check
authenticated with Parse.User objects that are obtained via an
authenticated method.
So when a user Signs up on my web app, get's a verification email which after clicking will direct the user back to the app. Then I call Parse.User.current() which returns the user object but the sessionToken it's empty. The user object looks like this:
ACL: {*: {…}, vIl61voP42: {…}}
createdAt: "2019-11-08T11:47:44.102Z"
emailVerified: true
objectId: "vIl61voP42"
origin: "Web App"
sessionToken: undefined
updatedAt: "2019-11-08T11:50:19.633Z"
username: "dfgg#dfgg.com"
My goal would be to allow the user to access the app without having to manually login and therefor create a session. From Parse docs my understanding was that once we have a user returned from Parse.User.current() would mean this user would be logged in and therefor I could set and save new data into the user object, which is not the case...
For sign up I've a standard Parse sign up as per documentation:
var user = new Parse.User();
user.set("username", "my name");
user.set("password", "my pass");
user.set("email", "email#example.com");
try {
await user.signUp();
// Hooray! Let them use the app now.
} catch (error) {
// Show the error message somewhere and let the user try again.
alert("Error: " + error.code + " " + error.message);
}
Once sign up form it's submitted the user receives a email confirmation link which will redirect them to my application url. This is how my Parse server settings looks like:
const api = new ParseServer ({
...
verifyUserEmails: true,
emailVerifyTokenValidityDuration: 0,
preventLoginWithUnverifiedEmail: true,
customPages: {
// App
passwordResetSuccess: settings["appUrl"] + "/login",
verifyEmailSuccess: settings["appUrl"] + "/dashboard",
linkSendSuccess: settings["appUrl"] + "/email-confirmation",
// Parse
invalidLink: settings["serverUrl"] + "/expired",
choosePassword: settings["serverUrl"] + "/choose_password"
}
...
})
Finally, once the user arrives at my dashboard route from the verification email link I get the current user via await Parse.User.current() which returns me the user object without a session token. Also checked on my local dev db and there's no session being created.
Any idea?
I am trying to add the sign up link to my okta login widget. I had aded the user registration policy in the okta admin dashboard. But when i am clicking on the Sign Up link it shows an empty dailog box.
On inspecting in console i got this error.
{"errorCode":"E0000007","errorSummary":"Not found: Resource not found: 0oahy3194bQu0CKAD0h7 (UserRegistrationPolicy)","errorLink":"E0000007","errorId":"oae2fIZi7s3SBanTYQBXE77tQ","errorCauses":[]}
I followed this link to add the user registration policy
https://help.okta.com/en/prod/Content/Topics/Directory/Directory_Self_Service_Registration.htm
I had configured the sign in widget in my Angular App using this code:
var signInWidgetConfig = {
// Enable or disable widget functionality with the following options. Some of these features require additional configuration in your Okta admin settings. Detailed information can be found here: https://github.com/okta/okta-signin-widget#okta-sign-in-widget
// Look and feel changes:
logo: '//logo.clearbit.com/okta.com', // Try changing "okta.com" to other domains, like: "workday.com", "splunk.com", or "delmonte.com"
language: 'en', // Try: [fr, de, es, ja, zh-CN] Full list: https://github.com/okta/okta-signin-widget#language-and-text
i18n: {
//Overrides default text when using English. Override other languages by adding additional sections.
'en': {
'primaryauth.title': 'Sign In', // Changes the sign in text
'primaryauth.submit': 'Sign In', // Changes the sign in button
// More e.g. [primaryauth.username.placeholder, primaryauth.password.placeholder, needhelp, etc.].
// Full list here: https://github.com/okta/okta-signin-widget/blob/master/packages/#okta/i18n/dist/properties/login.properties
}
},
// Changes to widget functionality
features: {
registration: true, // Enable self-service registration flow
rememberMe: true, // Setting to false will remove the checkbox to save username
//multiOptionalFactorEnroll: true, // Allow users to enroll in multiple optional factors before finishing the authentication flow.
//selfServiceUnlock: true, // Will enable unlock in addition to forgotten password
//smsRecovery: true, // Enable SMS-based account recovery
//callRecovery: true, // Enable voice call-based account recovery
router: false // Leave this set to true for the API demo
},
authScheme:'SESSION',
baseUrl: sampleConfig.oidc.issuer.split('/oauth2')[0],
clientId: sampleConfig.oidc.clientId,
redirectUri: sampleConfig.oidc.redirectUri,
authParams: {
responseType: ['id_token', 'token'],
issuer: sampleConfig.oidc.issuer,
display: 'page',
scopes: sampleConfig.oidc.scope.split(' '),
},
};
this.signIn = new OktaSignIn(signInWidgetConfig);
Please help me in this.
I am using the Parse Server Simple Mailgun Adapter, and my Parse Server is working perfectly on Heroku. I am new to node.js and Express, but I installed the adapter on the root of the Parse Server via:
npm i parse-server-simple-mailgun-adapter
This created a node_modules folder and essentially cloned the Github repository for the Mailgun Adapter. My index.js Parse Server configuration looks like:
var api = new ParseServer({
verifyUserEmails: true,
databaseURI: databaseUri || 'mongodb://DATABASE',
cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js',
appId: process.env.APP_ID || 'APPID',
masterKey: process.env.MASTER_KEY || 'MASTERKEY', //Add your master key here. Keep it secret!
serverURL: process.env.SERVER_URL || 'https://SERVER/parse', // Don't forget to change to https if needed
publicServerURL: 'https://SERVER/parse',
fileKey: process.env.FILE_KEY || 'FILEKEY',
push: {
ios: [
{
pfx: 'FILE.p12', // Dev PFX or P12
bundleId: 'BUNDLE',
production: false // Dev
},
{
pfx: 'FILE.p12', // Prod PFX or P12
bundleId: 'BUNDLE',
production: true // Prod
}
]
},
emailAdapter: {
module: 'parse-server-simple-mailgun-adapter',
options: {
fromAddress: 'EMAIL#DOMAIN',
domain: 'DOMAIN',
apiKey: 'KEY',
}
},
liveQuery: {
classNames: ["Posts", "Comments"] // List of classes to support for query subscriptions
}
});
The server works perfectly when commenting out the verifyUserEmails key. With it, the server will not work. The Mailgun adapter does not work regardless. Any help would be greatly appreciated. Thanks!
Did you set up the email adapter?
Take a look at : https://github.com/ParsePlatform/parse-server
Email verification and password reset
Verifying user email addresses and enabling password reset via email requries an email adapter. As part of the parse-server package we provide an adapter for sending email through Mailgun. To use it, sign up for Mailgun, and add this to your initialization code:
var server = ParseServer({
...otherOptions,
// Enable email verification
verifyUserEmails: true,
// The public URL of your app.
// This will appear in the link that is used to verify email addresses and reset passwords.
// Set the mount path as it is in serverURL
publicServerURL: 'https://example.com/parse',
// Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'Parse App',
// The email adapter
emailAdapter: {
module: 'parse-server-simple-mailgun-adapter',
options: {
// The address that your emails come from
fromAddress: 'parse#example.com',
// Your domain from mailgun.com
domain: 'example.com',
// Your API key from mailgun.com
apiKey: 'key-mykey',
}
}
});
You can also use other email adapters contributed by the community such as parse-server-sendgrid-adapter or parse-server-mandrill-adapter.
OR
Create your own in cloud code using mailgun-js
https://www.npmjs.com/package/mailgun-js
var api_key = '[SECRET_API_KEY]';
var domain = '[DOMAIN_HERE]';
var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
Parse.Cloud.define('testemail', function(req, res) {
var data = {
from: 'Excited User <me#samples.mailgun.org>',
to: 'foo#bar.com',
subject: 'Hello',
text: 'Testing some Mailgun awesomness!'
};
mailgun.messages().send(data, function (error, body) {
console.log(body);
});
res.success('Email Sent!');
});
The real reason why this happens is because you need to include the appName in your server initialization (this drove me crazy for hours)
appName: 'yourAppName',