strapi reset password alwasy call localhost - strapi

I configured template to use URL to send reset password email.
I alwasy got this email with link to localhost so on the server it does not work.
I checked the implementation and seems there this is no way to override it:
const url = `${getAbsoluteAdminUrl(
strapi.config
)}/auth/reset-password?code=${resetPasswordToken}`;
return strapi
.plugin('email')
.service('email')
.sendTemplatedEmail(
{
to: user.email,
from: strapi.config.get('admin.forgotPassword.from'),
replyTo: strapi.config.get('admin.forgotPassword.replyTo'),
},
strapi.config.get('admin.forgotPassword.emailTemplate'),
{
url,
user: _.pick(user, ['email', 'firstname', 'lastname', 'username']),
}
)
.catch(err => {
// log error server side but do not disclose it to the user to avoid leaking informations
strapi.log.error(err);
});
};
and
const getAbsoluteUrl = adminOrServer => (config, forAdminBuild = false) => {
const { serverUrl, adminUrl } = getConfigUrls(config, forAdminBuild);
let url = adminOrServer === 'server' ? serverUrl : adminUrl;
if (url.startsWith('http')) {
return url;
}
let hostname =
config.get('environment') === 'development' &&
['127.0.0.1', '0.0.0.0'].includes(config.get('server.host'))
? 'localhost'
: config.get('server.host');
return `http://${hostname}:${config.get('server.port')}${url}`;
};

The url can be set from the admin panel: General > Settings > Users & Permissions > Advanced settings > Reset password page.
You can use https://www.npmjs.com/package/strapi-plugin-config-sync to sync this across your environments.

Related

NextAuth getSession from subdomain not working

I am building a platform that offers different applications, the main platform is running on http://localhost and the applications will run each on a specific subdomain, at the moment I have an application running on http://sub.localhost.
I am using Nginx and Docker to host both the platform and the application, my goal would be to authenticate on http://localhost and use the session of the platform in the applications (subdomains), I have already taken a look at every single source/similar problem but could not find a solution, some of the sources I have read are the following:
https://github.com/nextauthjs/next-auth/discussions/1299
https://github.com/nextauthjs/next-auth/issues/405
https://github.com/nextauthjs/next-auth/issues/2718
At the moment this is my .env.local on the main platform:
NODE_ENV=development
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_AUTH_URL=...
NEXTAUTH_URL=http://localhost/
NEXTAUTH_URL_INTERNAL=http://mygames:3000/
NEXTAUTH_SECRET=...
DATABASE_URL=...
NEXT_PUBLIC_API_KEY=...
NEXT_SECRET_API_KEY=...
The following is the .env.local of the application (subdomain):
NEXTAUTH_URL=http://sub.localhost/
NEXTAUTH_URL_INTERNAL=http://mygames:3000/
NEXTAUTH_SECRET=...
DATABASE_URL=...
NEXT_PUBLIC_API_KEY=...
NEXT_SECRET_API_KEY=...
The following is my [...nextauth].js for the main platform:
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { PrismaAdapter } from '#next-auth/prisma-adapter';
import prisma from '../../../lib/prisma';
import Stripe from 'stripe';
const getDomainWithoutSubdomain = url => {
const urlParts = new URL(url).hostname.split('.');
return urlParts
.slice(0)
.slice(-(urlParts.length === 4 ? 3 : 2))
.join('.');
};
const hostName = getDomainWithoutSubdomain(process.env.NEXTAUTH_URL);
console.log("HOSTNAME", hostName);
const options = {
secret: process.env.NEXTAUTH_SECRET,
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
pages: {
signIn: '/signin'
},
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return true;
},
async redirect({ url, baseUrl }) {
return baseUrl;
},
async session({ session, user, token }) {
return { ...session, ...user };
},
async jwt({ token, user, account, profile, isNewUser }) {
return token;
}
},
cookies: {
sessionToken: {
name: process.env.NODE_ENV === 'production' ? `__Secure-next-auth.session-token` : 'next-auth.session-token',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production' ? true: false,
domain: '.' + hostName
}
}
}
}
export default (req, res) => NextAuth(req, res, options)
When I use getSession in the subdomain application I receive a null object, what can I do to solve this? Feel free to ask for anything for more details!
Spent ages looking for a solution...
Solution:
https://github.com/nextauthjs/next-auth/discussions/4089#discussioncomment-2290660
TLDR; You cannot use localhost subdomains as intended. You must use example.com and app.example.com. To set these go to the hosts file in you system.
Follow the steps in the github post if needed

Strapi returns 404 for custom route only when deployed to Heroku

I have created a custom route in Strapi v4 called "user-screens". Locally I hit it with my FE code and it returns some data as expected. However when I deploy it to Heroku and attempt to access the endpoint with code also deployed to Heroku it returns a 404. I've tailed the Heroku logs and can see that the endpoint is hit on the server side, but the logs don't give anymore info other than it returned a 404.
I am doing other non custom route api calls and these all work fine on Heroku. I am able to auth, save the token, and hit the api with the JWT token and all other endpoints return data. This is only happening on my custom route when deployed to Heroku. I've set up cors with the appropriate origins, and I am wondering if I need to add something to my policies and middlewares in the custom route. I have verified the permissions and verified the route is accessible to authenticated users in the Strapi admin.
Here is my route:
module.exports = {
routes: [
{
method: "GET",
path: "/user-screens",
handler: "user-screens.getUserScreens",
config: {
policies: [],
middlewares: [],
},
},
],
};
And my controller:
"use strict";
/**
* A set of functions called "actions" for `user-screens`
*/
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
ctx.send(result);
});
},
};
For anyone facing this, the answer was to change how I returned the ctx response from a 'send' to a 'return' from the controller method. I am not sure why this works locally and not on Heroku, but this fixes it:
New controller code:
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
return strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
return result;
})
.catch((error) => {
return error;
});
},
};

Cypress: How can we bypass cross origin error in Firefox? (cross origin issue, outside cy.origin)

Part 1 question: Cypress: How can we bypass cross origin error in Firefox? (iframes)
Hi, this is the part two for my question. I am also currently testing download functionality which needs to go to a different domain to check. I also list the format of the webpages. I altered my code a bit to not include sensitive information.
The process is:
From original application website, user will download the course (starting.originalWebsite.com)
Download link will be sent to the user's email, contains iframe (emailWebsite.com)
Mail contains username and password to access the document from the download link (download.originalWebsite.com)
User checks the contents of the downloaded document
The test stops after visiting the new link (original link: starting.originalWebsite.com, new link: emailWebsite.com) because it's already outside cy.origin() syntax. My problem with cy.origin() is that the email contains iframes and cy.origin() views the iframe as its native page. Cypress cannot see the original native page anymore and I cannot anymore interact with it. I also have the chromeWebSecurity set to false.
The code below works in Chrome but not in Firefox because we're getting the error below. What can I possibly do for this? So Firefox will allow my testing. Thank you!
And('user goes to mailtrap, logins and checks for the email link', () => {
const credentials = {
username: testdata.credentials.username,
password: testdata.credentials.password
}
cy.origin(Cypress.config().mailtrapUrl, { args: credentials }, ({ username, password }) => {
cy.visit(Cypress.config().mailtrapUrl)
})
mailtrapPO.getSignin().click()
cy.explicitWait()
mailtrapPO.getEmailField().scrollIntoView().click().type(testdata.credentials.username)
mailtrapPO.getNextButton().scrollIntoView().click()
cy.explicitWait()
mailtrapPO.getPasswordField().scrollIntoView().click().type(testdata.credentials.password)
mailtrapPO.getLoginButton().scrollIntoView().click()
cy.explicitWait()
mailtrapPO.getTransferStagingInbox().click()
cy.explicitWait()
mailtrapPO.getMessageList()
.find('span')
.contains('mail title')
.eq(0)
.scrollIntoView()
.click({ force: true })
mailtrapPO.getHTMLSourceTab().click({ force: true })
mailtrapPO.getCode().children().within((code) => {
var downloadUrl = Cypress.env('downloadCoursesUrl')
cy.get('span').contains('Password: ').scrollIntoView().invoke('text').then((documentDownloadPassword) => {
cy.wrap(documentDownloadPassword).as('passwordHint')
cy.log(documentDownloadPassword)
cy.get('#passwordHint').then((downloadPassword) => {
var delimeter = 'Password: ',
passwordIndex = delimeter.length
cy.wrap(downloadPassword.substring(passwordIndex)).as('documentPassword')
})
})
cy.get('span').contains(downloadUrl).scrollIntoView().invoke('text').then((documentDownloadLink) => {
cy.wrap(documentDownloadLink).as('downloadLink')
cy.log(documentDownloadLink)
cy.get('#downloadLink').then((link) => {
var cleanLink = link.replaceAll('"', '')
cy.log(cleanLink)
var delimeter = Cypress.env('downloadhmmm'),
linkFirstIndex = delimeter.length
cy.log(linkFirstIndex)
cy.wrap(cleanLink.substring(linkFirstIndex)).as('downloadhmmm')
})
})
})
cy.get('#downloadhmmm').then((finalLink) => {
cy.get('#documentPassword').then((password) => {
const linkBody = {
username: testdata.credentials.username,
password: password,
downloadlink: finalLink,
}
cy.origin(Cypress.env('transferUrl'), { args: linkBody }, ({ username, password, downloadlink }) => {
cy.visit(downloadlink, {
auth: {
username: username,
password: password
}
})
// to prevent Cypress from loading indefinitely when downloading, only works in Chrome
cy.window().document().then(function (doc) {
doc.addEventListener('click', () => {
setTimeout(function () { doc.location.reload() }, 5000)
})
cy.get('#downloadButton').click({ force: true })
cy.wait(4000)
})
})
})
})
cy.get('span').contains('courses').invoke('text').then((title) => {
cy.wrap(title).as('initialDocumentTitle')
cy.get('#initialDocumentTitle').then((initialTitle) => {
var convertedTitle = initialTitle.replaceAll(':', "_")
cy.wrap(convertedTitle).as('downloadedDocumentTitle')
cy.wait(10000)
})
})
cy.get('#courseNameArray').then((courseNamesArray) => {
cy.log(courseNamesArray)
cy.get('#downloadedDocumentTitle').then((documentTitle) => {
//start of reading files from Excel
var removeSpace = documentTitle.replace(/\s/g, '')
var excelFilePath = "cypress/downloads/" + removeSpace
cy.verifyDownload(removeSpace, { interval: 600 })
cy.wrap(excelFilePath).as('filePath')
cy.get('#filePath').then((filePath) => {
cy.task('checkExcelSheetContents', { filePath }).then((contents) => {
cy.log(contents)
expect(contents).to.deep.equal(courseNamesArray)
})
})
})
})

AngularFireAuthGuard redirectUrl after login

I use firebase and AngularFireAuthGuard to protect specific routes, so that only authenticated users are allowed to access them.
In particular, my MainComponent and MgmtComponent should only be accessible to AUTHENTICATED users.
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['/login']);
const routes: Routes = [
{ path: 'teams/:teamId/sessions/:sessionId',
component: MainComponent,
canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectUnauthorizedToLogin }
},
{ path: 'mgmt',
component: MgmtComponent,
canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectUnauthorizedToLogin }
},
{
path: 'login',
component: LoginComponent
}
];
My Problem is, that the user is not redirected back to the originally requested URL, after a successful login.
So what I want/expect is:
user goes to /mgmt
as the user is not authenticated he is automatically redirected to /login
user authenticates (e.g. via google or Facebook OAuth)
user is automatically redirected back to the originally requested page (/mgmt)
Steps 1-3 work fine, but step 4 is missing.
Now that the feature request is in, you can do this using the auth guard. However, the docs are unclear, so here is how I did it.
/** add redirect URL to login */
const redirectUnauthorizedToLogin = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
return redirectUnauthorizedTo(`/login?redirectTo=${state.url}`);
};
/** Uses the redirectTo query parameter if available to redirect logged in users, or defaults to '/' */
const redirectLoggedInToPreviousPage = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
let redirectUrl = '/';
try {
const redirectToUrl = new URL(state.url, location.origin);
const params = new URLSearchParams(redirectToUrl.search);
redirectUrl = params.get('redirectTo') || '/';
} catch (err) {
// invalid URL
}
return redirectLoggedInTo(redirectUrl);
};
This is an open feature request, the angularfire team is working on it: https://github.com/angular/angularfire/pull/2448
Meanwhile I found this workaround:
In the app-routing-module.ts instead of
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['/login']);
I use following to store the url in the sessionStorage:
const redirectUnauthorizedToLogin = (route: ActivatedRouteSnapshot) => {
const path = route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/');
return pipe(
loggedIn,
tap((isLoggedIn) => {
if (!isLoggedIn) {
console.log('Saving afterLogin path', path);
sessionStorage.setItem('afterLogin', path);
}
}),
map(loggedIn => loggedIn || ['/login'])
);
};
In the LoginComponent I read the value from the session storage to redirect:
sessionStorage.getItem('afterLogin');
this.router.navigateByUrl(redirectUrl);

Cannot connect Ember Simple Auth and DRF Token Auth

I have a trouble with Ember Simple Auth.
I'm trying to connect my server-side application, which working on Django 1.9 with DRF, and client-side which working on Ember 2.2.
On server side I'm obtaining token on 'http://localhost:8000/api-token-auth/'. Function requires two args from request: "username" and "password". But Ember Simple Auth send POST request with args: "username[identification]" and "password[password]", and server returns "400". I think that problem with arguments keys.
POST request
Responce
I tried to change .authenticate method in oauth2-password-grant.js(i can't write custom authenticator because i'm newbee in javascript), but nothing changed.
Manually POST request returns expected answer.
Please tell me the way to solve this problem.
And please forgive me for my english.
authenticate(identification, password, scope = []) {
return new RSVP.Promise((resolve, reject) => {
const data = { 'grant_type': 'password', username: identification, password };
const serverTokenEndpoint = this.get('serverTokenEndpoint');
const scopesString = Ember.makeArray(scope).join(' ');
if (!Ember.isEmpty(scopesString)) {
data.scope = scopesString;
}
this.makeRequest(serverTokenEndpoint, data).then((response) => {
run(() => {
const expiresAt = this._absolutizeExpirationTime(response['expires_in']);
this._scheduleAccessTokenRefresh(response['expires_in'], expiresAt, response['refresh_token']);
if (!isEmpty(expiresAt)) {
response = Ember.merge(response, { 'expires_at': expiresAt });
}
resolve(response);
});
}, (xhr) => {
run(null, reject, xhr.responseJSON || xhr.responseText);
});
});
},
My variant:
const data = { 'grant_type': 'password', 'username': identification, 'password': password };
authenticate: function () {
// var username = this.getProperties('username');
// var password = this.getProperties('password');
const {username, password} = this.getProperties('username', 'password');
this.get('session').authenticate('authenticator:oauth2', username, password).catch((reason) => {
this.set('errorMessage', reason.error || reason);
});
}
It was my mistake.

Resources