I am working with a system which's 2 years old already using a layered approach (controllers, services, models, repos).
I have a set of requirements for sending several welcome/onbording emails based on actions on the system, specially on user-signup and user-password-reset-requested.
In the current implementation from the older team they do the following:
Signup the user
Send a basic welcome email, the same for everybody
That approach is simple, now I need to check for several fields in different entities to discover which email has to be sent. For me the service above is likely violating the SRP (single responsability pattern) and maybe the task of sending emails looks like more a side-effect. This is my current approach:
Signed the user, Blank the user password on reset
Trigger to SQS an user-signed-up and user-password-reset-requested events
Listen the event and process the email
I feel like adding this extra EDA (event-driven architecture) idea, brings the benefit of decoupling the app service, and reliability, since if the email failed, the user was created/resetted but no email was sent. Since I may retry on errors I will not loose any email, in fact I used the outbox pattern to make it more transactional to the user change.
export class UserService {
public async blankUserPasswordByEmail(email: string): Promise<void> {
await this.getManager().transaction(async (entityManager) => {
const userRepo = entityManager.getCustomRepository(UserRepository);
const outboxRepo = entityManager.getCustomRepository(OutboxRepository);
const user = await userRepo.findByEmail(email);
const outboxMessage = new Outbox();
outboxMessage.entityId = user.id;
outboxMessage.entityType = 'User';
outboxMessage.eventType = EventType.USER_PASSWORD_RESET_REQUESTED;
outboxMessage.eventPayload = JSON.stringify(new AfterUserPasswordResetRequestedEvent(user.id));
user.password = null;
await userRepo.save(user);
await outboxRepo.save(outboxMessage);
});
}
}
And the event handler:
export class AfterUserPasswordResetRequestedListener extends BaseListener<AfterUserPasswordResetRequestedEvent> {
protected queueUrl: string = process.env.AWS_SQS_AFTER_USER_PASSWORD_RESET_REQUESTED_URL;
protected visibilityTimeout: number = 60;
protected workerName: string = 'After User Password Reset Requested Handler';
protected batchSize: number = undefined;
constructor(
private readonly emailService = new EmailService(),
private readonly userService = new UserService(),
private readonly authService = new AuthService()
) {
super();
}
async onMessage(message: AfterUserPasswordResetRequestedEvent): Promise<void> {
try {
const user = await this.userService.findUserById(message.userId);
const loginUrl = await this.authService.createResetPasswordTokenizedUrl(user.email, process.env.FRONTEND_URL);
await this.emailService.send(
new PasswordResetEmail(
{
name: user.name,
loginurl: loginUrl,
},
user.email
)
);
} catch (err) {
APP_LOGGER.error(err);
if (process.env.NODE_ENV === 'production') {
Sentry.captureException(err);
}
throw err;
}
}
}
Is that an overkill? I feel like guilty with both approaches. Using IOC for managing emails increase my SRP problem (small) and unreliable send emails and using EDA looks like more robust and complex but it solves the issue very well. I am also thinking if I am not adding things that may wrongly mixing concepts from both EDA/Layered world.
Any experience with that? It looks like some people did it https://www.toptal.com/software/single-responsibility-principle
Related
This question already has answers here:
Why caching access token is consider bad in oauth2?
(4 answers)
Closed 1 year ago.
I've set up a firebase passport strategy on a NestJS server which works fine, but I did not like the long load times it would incur on all requests that went through it. So I decided to cache decoded tokens until they are expired, and this this massively reduced load times for valid and unexpired tokens.
However, I am concerned that there might be security risks connected to this. Mostly because this seemed like such a simple addition to me, someone must have thought about it before. I assume the people who made the firebase sdk must have considered adding it as a feature, but why haven't they?
For reference, here's the code for my passport strategy:
#Injectable()
export class FirebaseAuthStrategy extends PassportStrategy(Strategy, 'firebase-auth') {
private defaultApp: any;
constructor(
private configService: ConfigService,
#Inject(CACHE_MANAGER) private cacheManager: Cache
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
});
const config = this.configService.get<string>('FIREBASE_CONFIG');
if (!config) {
throw new Error('FIREBASE_CONFIG not available. Please ensure the variable is supplied in the `.env` file.');
}
const firebase_params = JSON.parse(config);
this.defaultApp = firebase.initializeApp({
credential: firebase.credential.cert(firebase_params)
});
}
async validate(token: string) {
const cachedFirebaseUser = await this.cacheManager.get(token);
if (cachedFirebaseUser) return cachedFirebaseUser;
const firebaseUser: any = await this.defaultApp
.auth()
.verifyIdToken(token, true)
.catch((err) => {
console.log(err);
throw new UnauthorizedException(err.Message);
});
if (!firebaseUser) {
throw new UnauthorizedException();
}
/**
* input parameter for `Date` or `moment` constructor is in milliseconds for unix timestamps.
* input * 1000 will instantiate correct Date or moment value.
* See here for reference: https://stackoverflow.com/a/45370395/5472560
*/
const exp = moment(+firebaseUser['exp'] * 1000);
const now = moment.now();
const ttl = exp.diff(now, 'seconds');
await this.cacheManager.set(token, firebaseUser, { ttl });
return firebaseUser;
}
}
As long as you don't use the decoded as a signal of authorization after the token has expired, it is perfectly safe to cache a decoded token. Caching ID tokens is a valid approach to prevent having to decode them on each call.
Goal
I'm attempting to build a multi-tenant application with Feathers JS. On login, the tenant ID will be included in the request. From then on, the tenant ID will be gotten from the user field with the request parameters. Every user has this tenantId field.
In MongoDB, I have one unique collection of every data type per tenant. The collection names look like tenantId.documents and tenantId.users
Problem
The service generated via the feathers generate service CLI command looks like so:
export class Documents extends Service {
//eslint-disable-next-line #typescript-eslint/no-unused-vars
constructor(options: Partial<MongoDBServiceOptions>, app: Application) {
super(options);
const client: Promise<Db> = app.get('mongoClient');
client.then(db => {
this.Model = db.collection('documents');
});
}
}
As you can see, the generated Services seem to need their collection name ("documents" in this case) during instantiation. Normally, this makes sense since it saves time awaiting a call to app.get("mongoClient")
However, since I need to dynamically change which collection I read from based on the User's tenantId, this won't work for me.
I implemented something like the following:
export class Documents extends Service {
client: Promise<Db>
//eslint-disable-next-line #typescript-eslint/no-unused-vars
constructor(options: Partial<MongoDBServiceOptions>, app: Application) {
super(options);
this.client = app.get("mongoClient");
}
async create(data: IDocumentData, params: Params) {
const db: Db = await this.client;
this.Model = db.collection(`${params.user!!.organizationId}.documents`);
return super.create(data, params);
}
}
The problems are these:
I need to await this.client every request, even when the promise will probably already be fulfilled by the time a user actually makes a request to this service
I have to implement every method of the parent Service even though I barely need to add any real functionality.
Question
What is the most feathers-y way to solve this problem?
I don't want to override every method that I need in every service
I don't see a way to handle this with middleware or hooks.
I also don't think it's necessary to create one service instance per tenant in my application. It seems wasteful since I don't need to make any additional external requests based on the tenant ID, I just need to change the collection
Is there a good, pretty way to do this in Feathers?
Thanks to the helpful Feather community Slack channel, I think I came across a halfway-decent solution to this specific issue. It doesn't address all of my concerns, but at least it de-clutters my code.
First, I should create a new class that extends the built in Service class that implements the feature that I want. It could look something like this:
class DynamicMongoService extends Service {
client: Promise<Db>;
collectionName: string;
constructor(
options: Partial<MongoDBServiceOptions>,
app: Application,
collectionName: string
) {
super(options);
this.client = app.get("mongoClient");
this.collectionName = collectionName;
}
async getCollection(params: Params) {
const db: Db = await this.client;
this.Model = db.collection(
`${params!!.user!!.organizationId}.${this.collectionName}`
);
}
async find(params?: Params) {
await this.getCollection(params!!);
return super.create(params!!);
}
async get(id: Id, params?: Params) {
await this.getCollection(params!!);
return super.get(id, params);
}
async create(data: Partial<any> | Array<Partial<any>>, params?: Params) {
await this.getCollection(params!!);
return super.create(data, params);
}
async update(id: NullableId, data: any, params?: Params) {
await this.getCollection(params!!);
return super.update(id!!, data, params);
}
async patch(id: NullableId, data: Partial<any>, params?: Params) {
await this.getCollection(params!!);
return super.patch(id!!, data, params);
}
async remove(id: NullableId, params?: Params) {
await this.getCollection(params!!);
return super.patch(id!!, params!!);
}
}
The key elements are thus:
Pass collection name in the constructor
Get the collection name before each method
An implementation of this service would look like this:
export class Documents extends DynamicMongoService {
constructor(options: Partial<MongoDBServiceOptions>, app: Application) {
super(options, app, "documents");
}
}
Not the best, but easy enough!
I am creating a chat bot using azure bot framework in Nodejs.
QnA maker to store question answers and one LUIS app.
Now I want to detect end of conversation(either by checking no reply from long time or refreshing a webpage) and add feedback card at the end of conversation.
You can achieve this by use of the onEndDialog method and the use of a separate class to manage the feedback process.
First, I have a component dialog that imports the feedback.js file and calls the associated onTurn() method within onEndDialog.
Next, I create the mainDialog.js file in which MainDialog extends FeedbackDialog. In this way, FeedbackDialog sits "on top" of MainDialog listening for specific user inputs or activities. In this case, it is listening for EndDialog() to be called. You will likely want to add additional validation to be sure it only fires when the EndDialg() you want is called.
Lastly, in the feedback.js file, this is where your feedback code/logic lives. For simplicity, I'm using a community project, botbuilder-feedback, for generating a user feedback interface. The majority of the code is focused on creating and managing the "base" dialog. Additional dialog activity comes from within the botbuilder-feedback package.
For reference, this code is based partly on the 13.core-bot sample found in the Botbuilder-Samples repo.
Hope of help!
feedbackDialog.js:
const { ComponentDialog } = require('botbuilder-dialogs');
const { Feedback } = require('./feedback');
class FeedbackDialog extends ComponentDialog {
constructor() {
super();
this.feedback = new Feedback();
}
async onEndDialog ( innerDc ) {
return await this.feedback.onTurn( innerDc );
}
}
module.exports.FeedbackDialog = FeedbackDialog;
mainDialog.js:
const { FeedbackDialog } = require( './feedbackDialog' );
class MainDialog extends FeedbackDialog {
[...]
}
module.exports.MainDialog = MainDialog;
feedback.js:
const { ActivityTypes } = require('botbuilder');
const { DialogTurnStatus } = require('botbuilder-dialogs');
const Botbuilder_Feedback = require('botbuilder-feedback').Feedback;
class Feedback {
async onTurn(turnContext, next) {
if (turnContext.activity.type === ActivityTypes.Message) {
await Botbuilder_Feedback.sendFeedbackActivity(turnContext, 'Please rate this dialog');
return { 'status': DialogTurnStatus.waiting };
} else {
return { 'status': DialogTurnStatus.cancelled };
}
await next();
};
}
module.exports.Feedback = Feedback;
I'm creating an action for Google Assistant with Dialogflow and actions-on-google-nodejs that accesses the GitKraken Glo API to add cards to people's boards. I'm authenticating my users with Account Linking. I want my users to be able to say things like Add a card to [board name] or Add a card. If a board name isn't given I want the action to prompt the user for it. How can I create a session entity that get's all the board names for the logged in user?
Sorry if this doesn't make much sense, I'm pretty new to Actions on
Google and Dialogflow. Feel free to ask questions for clarity.
There are a few things you'll need to do first to use a Session Entity:
The Entity Type needs to already exist. Session Entities update existing ones. The easiest way to do this is to create the Entity you want in the Dialogflow UI. It doesn't need to have any Entities in it, but having one as a default can be useful.
You need a Service Account for your project in Google Cloud that will do the update, and a secret key for this account.
Your life will be a lot easier if you use a library, such as the dialogflow-nodejs library.
In general, your code needs to do the following, typically when the user first starts the session (ie - in your Welcome Intent Handler):
Get the list of boards
Update the Session Entity Type, creating an Entity for each board. Doing this update involves:
Issuing a patch against the projects.agent.sessions.entityTypes method with a SessionEntityType for the Entity Type you're overriding.
The SessionEntityType will contain an array of Entities with the canonical name (likely the board name, or some unique identifier) and any aliases for it (at least the board name, possibly anything else, possibly including aliases like "the first one" or "the most recent one").
The README for the library includes links to sample code about how to do this using the nodejs library. Code that I have that does this work has a function like this:
function setSessionEntity( env, entityType ){
const config = envToConfig( env );
const client = new dialogflow.SessionEntityTypesClient( config );
let parent = env.dialogflow.parent;
if( entityType.displayName && !entityType.name ){
entityType.name = `${parent}/entityTypes/${entityType.displayName}`;
}
if( !entityType.entityOverrideMode ){
entityType.entityOverrideMode = 'ENTITY_OVERRIDE_MODE_OVERRIDE';
}
const request = {
parent: parent,
sessionEntityType: entityType
};
return client.createSessionEntityType( request );
}
conv.user.email
You can use conv.user object :
const Users = {};
app.intent('Get Signin', (conv, params, signin) => {
if (signin.status === 'OK') {
const email = conv.user.email
Users[email] = { };
conv.ask(`I got your email as ${email}. What do you want to do next?`)
} else {
conv.ask(`I won't be able to save your data, but what do you want to next?`)
}
})
app.intent('actions.intent.TEXT', (conv, input) => {
if (signin.status === 'OK') {
Users[conv.user.email] = {
lastinput: input
};
}
});
conv.id
Also with conv id is unique id for the current conversation.
// Create an app instance
const app = dialogflow()
// Register handlers for Dialogflow intents
const Users = {};
app.intent('Default Welcome Intent', conv => {
Users[conv.id] = {
conversationId: conv.id,
name: '1234'
};
})
app.intent('actions.intent.TEXT', (conv, input) => {
Users[conv.id] = {
lastinput: input
};
});
app.intent('Goodbye', conv => {
delete Users[conv.id];
})
I'm looking to send the user an SMS when reseting their password. I already have the facilities to send a SMS, I just need a guide on how to set it up with Identity 2.0. I can't seem to find any useful info online, the reference code itself isn't properly commented either.
I want to generate a security code, send it to the user, he must then input it into a form and then be allowed to reset his/her password. Can anyone direct me to a guide/tutorial that explains this process?
After digging in the identity source code i found an alternative token provider that can generate tokens similar to phone number confirmation (six digits).
I had to implement two methods in my UserManager to generate the code and then to validate it.
I declared the token provider inside the UserManager
private TotpSecurityStampBasedTokenProvider<User, string> smsResetTokenProvider = new TotpSecurityStampBasedTokenProvider<User, string>();
This is the first method to generate the code:
public async Task<string> GenerateSMSPasswordResetToken(string userId)
{
var user = await base.FindByIdAsync(userId);
var token = await smsResetTokenProvider.GenerateAsync("Reset Password", this, user);
return token;
}
This is the second method to validate the code:
public async Task<IdentityResult> SMSPasswordResetAsync(string userId, string token, string newPassword)
{
var user = await base.FindByIdAsync(userId);
var valid = await smsResetTokenProvider.ValidateAsync("Reset Password", token, this, user);
if (valid)
{
var passwordStore = Store as IUserPasswordStore<User, string>;
var result = await UpdatePassword(passwordStore, user, newPassword);
if (!result.Succeeded)
{
return result;
}
return await UpdateAsync(user);
}
else
{
return IdentityResult.Failed("InvalidToken");
}
}
You may need to tweak the code depending on your user manager