I'm using AzureMobileServices custom API in my hybrid app. The customAPI name is lets say 'userUtils'.
The login and authentication part works good, I am able to authenticate user using facebook/google oAuth.
After login, in its done function , I call a customAPI function - insert (a http POST) - that basically adds the user in docDB if not already added. and returns back user details to my js client.
(I've 'POST' permission set to 'users with application key' for this customAPI ( btw, is this permission setting right or should it be 'only authenticated users'?)).
//below code is part of a typescript file
this._zumoClient = new WindowsAzure.MobileServiceClient( "https://abcdefExample.azure-mobile.net/", "someExampleApplicationKey");
...
....
....
public authenicateUser(p_oAuthProvider: string) {
var p: Promise<any> = new Promise(
(resolve: (result: any) => void, reject: (err: any) => void) =>
this._zumoClient.login(p_oAuthProvider, null).done(
(loginResult) => {
console.log("login returned with result : " + loginResult.userId);
var theUserAuthId = loginResult.userId;
this._zumoClient.invokeApi('UserUtils/insert', {
method: 'POST',
body: { userAuthId: theUserAuthId }
})
.done(
(loginResult: any) => {
resolve(loginResult)
},
(loginErr: any) => {
reject(loginErr);
}
);
},
(loginErr: any) => {
reject(loginErr);
}
)
);
return p;
}
this first 'invokeAPI' call to my customAPI works good.
However, at a later point, I try to call another customAPI 'usersSocialContacts' (a http GET), I get 401 - 'unauthorized request' ( I've 'get' permission set to 'only authenticated users' for this customAPI).
public getUserSocialContacts() {
var p: Promise<any> = new Promise(
(resolve: (result: any) => void, reject: (err: any) => void) =>
this._zumoClient.invokeApi('UserUtils/userSocialContacts', {
method: 'GET'
})
.done(
(contactsResult: any) => {
console.log("got contacts -" + JSON.stringify(contactsResult));
resolve(contactsResult);
},
(contatcsErr: any) => {
reject(contatcsErr);
}
)
);
return p;
}
And if I set the GET permission for this customAPI to 'allow users with the Application key', then the api function gets called but the request.user is undefined.
How does this work, how do I let the customAPI let know that this user has been authenticated and pass the user in every request, from js client. I read some suggestions in some SO questions or otherwise in google search, to cache the user's auth token and pass it in every request to zumo services using an optional filter object in login call, found some examples for C#/.net, but didn't find any clear example or documentation for javascript client, maybe I missed. Azure documentation gives a js example of calling a customAPI from js client, but that doesn't show caching part and sending authenticated user in subsequent requests.
Would appreciate any knowledge sharing or any pointers to right documentation in this regard.
If you would like only logged in users to access the custom api, then you need to set the permission to 'only authenticated users'.
Once the user is logged in, if you are using the same instance of MobileServiceClient (without logging out) then any requests made to zumo service should contain zumo auth token.
Related
I use SockJS and StompJS and when I open my application in the browser sometimes it tries to subscribe to some topics before it even connected to the websocket. I'd like the topic subscription wait until the app is connected to websocket.
export class SocksService {
...
public subscribe<T>(destination: string, callback?: (body: T) => void, headers?: object): Observable<Subscription> {
const subscription = new Subject<Subscription>();
headers = headers || {};
this.status.pipe(
first(status => status === ConnectionStatus.CONNECTED)
).subscribe((status: ConnectionStatus) => {
subscription.next(this.stompClient.subscribe(destination, (message: Message) => {
if (callback) {
callback(JSON.parse(message.body) as T);
}
}, headers));
});
return subscription.asObservable();
}
...
}
That's why I implemented this code and I call it like:
this.socksService.subscribe<User>('/topic/user', (user: User) => {
console.log('user received', user);
}).subscribe(subscription => this.userSubscription = subscription);
So I subscribe to the topic only when the connection status is connected and it will be called only for the first time the client successfully connects.
I'd like to unsubscribe later from the topic, so I need the Subscription object returned by the inner subscribe and I also need the message from the inner subscribe.
What I implemented works good, but I think there must be a better way to do it.
(I tried rx-stomp, but it has many bugs.)
You said you tried rx-stomp but have you tried the Angular2+ version of rx-stomp, called ng2-stompjs? It exposes key classes as Angular Injectable Services.
See the mention of it under rx-stomp, here:
https://github.com/stomp-js/ng2-stompjs#documentation
...and it's implementation (Angular 7) via a nice step-by-step, here:
https://stomp-js.github.io/guide/ng2-stompjs/ng2-stomp-with-angular7.html#receiving-messages
e.g.:
this.rxStompService.watch('/topic/demo').subscribe((message: Message) => {
this.receivedMessages.push(message.body);
});
I'm currently trying to implement in my Angular app the connection to Strava API.
To resume quickly:
User clicks on a button to connect to Strava
It is redirected to Strava for authentication(using PKCE)
Strava redirects to my app with a code
In the ngoninit I'm checking for route params and if I have the code, I launch two promises chained: the first one to get the Access Token from Strava then the recording into a DB(Firebase).
The problem is that sometimes the data is recorded in firebase and sometimes it is not. The behavior is not systematic. Strange thing is that I go into my postNewToken everytime because the console logs it.
If I just record to firebase (without strava token request) in ngOnInit(), it is created in 100% of the cases.
If I have a button that launches the token request and record into firebase, it seems to work everytime.
I have no idea how to solve it. It seems more a question of chaining promises into ngOnInit but I have no idea even how to bypass it.
The code from my component:
ngOnInit() {
const stravaCode = this.route.snapshot.queryParamMap.get('code');
if (stravaCode !== undefined) {
this.stravaService.handleStravaAuthorizationCode(stravaCode);
}
And in the service associated:
// Handle Strava Code received
handleStravaAuthorizationCode(authorizationCode: string) {
this.getStravaToken(authorizationCode).then(res => {
this.postNewToken(res).then(res => {
this.router.navigate(['encoding']);
});
});
}
// Get Strava access token to make the requests to the API -> only done once
getStravaToken(authorizationCode: string){
if (authorizationCode !== undefined){
console.log('Authorization code: ' + authorizationCode);
const data = {
client_id: environment.strava.client_id,
client_secret: environment.strava.client_secret,
code: authorizationCode,
grant_type: 'authorization_code'
};
return this.http.post<StravaToken>(this.stravaTokenURL, data)
.pipe(catchError(this.errorService.handleHttpError)).toPromise();
}
}
postNewToken(stravaToken: StravaToken) {
if (this.authService.isLoggedIn) {
console.log('Recording strava token into Firebase');
console.log(stravaToken);
return this.afs.collection('strava_tokens')
.add(stravaToken).then(res => console.log(res), err => console.log(err));
} else {
return Promise.reject(new Error('No User Logged In!'));
}
}
Finally, I understood.
I simply was not waiting for the connection to firebase to be established. So, I could not post any new data because I was not authenticated.
I have an Angular 9 web app and so far I have made it work quite nicely with Firestore. I have a form for adding user which saves the user to Firestore correctly but without adding them to Authenticated users, the add feature will be removed when I fix the issue below.
users.component.ts:
this.userService.addUser(user);
and in the userService I call:
async addUser(user: User) {
await this.firestore.collection('users').doc(user.email).set(user);
}
The problem is when I want to register a new user with FirebaseAuthentication, the user data does not get saved in users collection. When user clicks on Register in register.component.ts I call:
register() {
this.authenticationService
.register(this.form.controls['email'].value, this.form.controls['password'].value)
.then(() => {
this.userService.addUser({
nickname: this.form.controls['nickname'].value,
email: this.form.controls['email'].value,
role: 'Customer',
});
})
.catch((error) => {
console.log(error);
});
}
The register method in authenticationService.ts:
register(email: string, password: string) {
return this.angularFireAuth.auth.createUserWithEmailAndPassword(email, password);
}
I have tried different approaches, using Promises, async / await, calling directly from register method in authenticationService.ts, using collection('users').add instead of set, using uid I get as a response as document uid instead of email.
I'm inclined to think that there is some kind of rollback mechanism, since I have subscribed to data$:
this.firestore
.collection<User>(this.path)
.snapshotChanges()
.pipe(
takeUntil(this.destroy$),
map((items) => {
return items.map((item) => {
const data = item.payload.doc.data() as User;
return {
...data,
};
});
})
)
.subscribe((data) => {
this.users$.next(data);
});
in register.component.ts ngOnInit:
this.userService.users$.subscribe((data) => {
console.log(data);
});
Which gave me some insight:
The subject triggers twice instantly, with 4 elements and then 3.
users.component.ts
constructor(private userService: UserService, public dialog: MatDialog) {}
register.component.ts
constructor(
private userService: UserService,
private formBuilder: FormBuilder,
private authenticationService: AuthenticationService
) {}
Both components are part of the same module, but for some reason if I navigate to users.component.ts and then to register.component.ts saving user works! It gets added to both Authenticated users and Firestore users collection.
I am not getting any errors at any point.
So I really have no idea what might be causing it to behave differently, but I need it to work from register.component.ts without first navigating to users.component.ts.
EDIT:
My rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// This rule allows anyone on the internet to view, edit, and delete
// all data in your Firestore database. It is useful for getting
// started, but it is configured to expire after 30 days because it
// leaves your app open to attackers. At that time, all client
// requests to your Firestore database will be denied.
//
// Make sure to write security rules for your app before that time, or else
// your app will lose access to your Firestore database
match /{document=**} {
allow read, write: if request.time < timestamp.date(2020, 8, 6);
}
}
}
If you see a rollback like this, it typically means that the server rejected the write operation based on the security rules you have specified for the database. So it seems like the user doesn't have permission to write.
You'll have to modify the security rules of the database to allow the user to write their profile data.
You'll have to manually add the created user to your collection.
After createUserWithEmailAndPassword() is sucesfully executed, you should call a custom function that get's the User Object and saves it to Firestore.
example:
register(email: string, password: string) {
let rs = this.angularFireAuth.auth.createUserWithEmailAndPassword(email, password);
return this.saveUser(rs.user)
}
saveUser(user){
const userRef: AngularFirestoreDocument<User> = this.angularFistore.doc(`users/${user.email}`);
userRef.get().subscribe(snapDoc =>{
if(!snapDoc.exists){
const data; //Initialize
//do anything else
return userRef.set(data, { merge: true })
})
}
I'm using https://github.com/Microsoft/BotFramework-WebChat/blob/master/README.md
I want the bot to receive a "join" message whenever the web-chat widget is displayed on the site.
The idea is that the human does not have to initiate the conversation. The bot can respond to somebody joining the conversation with a welcome message.
How is this possible?
This "Welcome feature" has been a long term discussion and topic since Webchat v3. It looks like it has been fixed 2 days ago with this pull request: https://github.com/Microsoft/BotFramework-WebChat/pull/1286
There is now a sample on how to do that, located here:
https://github.com/Microsoft/BotFramework-WebChat/blob/master/samples/15.d.backchannel-send-welcome-event/index.html
In a few words, the demo is the following:
(async function () {
// In this demo, we are using Direct Line token from MockBot.
// To talk to your bot, you should use the token exchanged using your Direct Line secret.
// You should never put the Direct Line secret in the browser or client app.
// https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication
const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
const { token } = await res.json();
// We are using a customized store to add hooks to connect event
const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
// When we receive DIRECT_LINE/CONNECT_FULFILLED action, we will send an event activity using WEB_CHAT/SEND_EVENT
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: { language: window.navigator.language }
}
});
}
return next(action);
});
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store
}, document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
Please note that due to the fact that this PR is quite new, it's not embedded in the latest release so you have to point to the master version of webchat.js file, not latest:
<script src="https://cdn.botframework.com/botframework-webchat/master/webchat.js"></script>
And it's working: your bot side is notified of an activity of type Event, where you will be able to reply to your user, before he typed anything:
I'm using NestJS CQRS recipe in order to manage interactions between two entities: User and UserProfile. The architecture is an API Gateway NestJS server + a NestJS server for each microservice (User, UserProfile, etc.).
I have already set up basic interactions through User and UserProfile modules on API Gateway with their own sagas/events/commands:
When a user is created, a user profile is created
When the user profile creation fails, the previously created user is deleted
In details:
In User module, CreateUser command raises a UserCreated event that is intercepted by User saga, which will trigger CreateUserProfile command (from UserProfile module).
If the latter fails, a UserProfileFailedToCreate event is raised and intercepted by UserProfile saga, which will trigger DeleteUser command (from User module).
Everything works fine.
If the CreateUser command fails, I resolve(Promise.reject(new HttpException(error, error.status)) which indicates to the end user that something went wrong during the user creation.
My problem is that I cannot replicate the same behavior for the CreateUserProfile command since the HTTP request promise has already been resolved from the first command, obviously.
So my question is: is there any way to make a command fail if a subsequent command fails in the saga? I understand that the HTTP request is totally disconnected from any subsequent commands triggered by a saga, but I want to know if anybody has already played with events or something else here to replicate this data flow?
One of the reasons I'm using CQRS, besides having a much cleaner code for data interactions among microservices, is to be able to rollback repositories actions in case any of the chained commands fails, which works fine. But I need a way to indicate to the end user that the chain went through an issue and was rollbacked.
UserController.ts
#Post('createUser')
async createUser(#Body() createUserDto: CreateUserDto): Promise<{user: IAuthUser, token: string}> {
const { authUser } = await this.authService.createAuthUser(createUserDto);
// this is executed after resolve() in CreateUserCommand
return {user: authUser, token: this.authService.createAccessTokenFromUser(authUser)};
}
UserService.ts
async createAuthUser(createUserDto: CreateUserDto): Promise<{authUser: IAuthUser}> {
return await this.commandBus
.execute(new CreateAuthUserCommand(createUserDto))
.catch(error => { throw new HttpException(error, error.status); });
}
CreateUserCommand.ts
async execute(command: CreateAuthUserCommand, resolve: (value?) => void) {
const { createUserDto } = command;
const createAuthUserDto: CreateAuthUserDto = {
email: createUserDto.email,
password: createUserDto.password,
phoneNumber: createUserDto.phoneNumber,
};
try {
const user = this.publisher.mergeObjectContext(
await this.client
.send<IAuthUser>({ cmd: 'createAuthUser' }, createAuthUserDto)
.toPromise()
.then((dbUser: IAuthUser) => {
const {password, passwordConfirm, ...publicUser} = Object.assign(dbUser, createUserDto);
return new AuthUser(publicUser);
}),
);
user.notifyCreated();
user.commit();
resolve(user); // <== This makes the HTTP request return its reponse
} catch (error) {
resolve(Promise.reject(error));
}
}
UserSagas.ts
authUserCreated = (event$: EventObservable<any>): Observable<ICommand> => {
return event$
.ofType(AuthUserCreatedEvent)
.pipe(
map(event => {
const createUserProfileDto: CreateUserProfileDto = {
avatarUrl: '',
firstName: event.authUser.firstName,
lastName: event.authUser.lastName,
nationality: '',
userId: event.authUser.id,
username: event.authUser.username,
};
return new CreateUserProfileCommand(createUserProfileDto);
}),
);
}
CreateUserProfileCommand.ts
async execute(command: CreateUserProfileCommand, resolve: (value?) => void) {
const { createUserProfileDto } = command;
try {
const userProfile = this.publisher.mergeObjectContext(
await this.client
.send<IUserProfile>({ cmd: 'createUserProfile' }, createUserProfileDto)
.toPromise()
.then((dbUserProfile: IUserProfile) => new UserProfile(dbUserProfile)),
);
userProfile.notifyCreated();
userProfile.commit();
resolve(userProfile);
} catch (error) {
const userProfile = this.publisher.mergeObjectContext(new UserProfile({id: createUserProfileDto.userId} as IUserProfile));
userProfile.notifyFailedToCreate();
userProfile.commit();
resolve(Promise.reject(new HttpException(error, 500)).catch(() => {}));
}
}
UserProfileSagas.ts
userProfileFailedToCreate = (event$: EventObservable<any>): Observable<ICommand> => {
return event$
.ofType(UserProfileFailedToCreateEvent)
.pipe(
map(event => {
return new DeleteAuthUserCommand(event.userProfile);
}),
);
}
DeleteUserCommand.ts
async execute(command: DeleteAuthUserCommand, resolve: (value?) => void) {
const { deleteAuthUserDto } = command;
try {
const user = this.publisher.mergeObjectContext(
await this.client
.send<IAuthUser>({ cmd: 'deleteAuthUser' }, deleteAuthUserDto)
.toPromise()
.then(() => new AuthUser({} as IAuthUser)),
);
user.notifyDeleted();
user.commit();
resolve(user);
} catch (error) {
resolve(Promise.reject(new HttpException(error, error.status)).catch(() => {}));
}
}
In DDD terms your creation of User and UserProfile constitutes a business transaction - a group of business operations/rules that must be consistent - that spans multiple microservices.
In that case returning the database User before the UserProfile has been created means you return data in an inconsistent state. This is not necessarily wrong, but you should handle this appropriately in the client if you do it like that.
I see three possible ways to deal with this scenario:
You let the Sagas run until they have executed commands that indicate the business transaction has ended, only then resolve an outcome for the client indicating either success or failure (e.g. in error details you can report which steps succeeded and which did not). So you do not yet resolve in CreateAuthUserCommand.
If it can take a long time for the UserProfile to be created (it could even have to be manually validated by a Moderator) then you might want to resolve the User in CreateAuthUserCommand and then subsequently have the client subscribe to UserProfile-related events. You need a mechanism for that, but it decouples the client from the running transaction, and it can do other stuff.
Alternatively you could break up the business transaction into two parts for which the client sends separate requests: one creates/returns the authenticated User, and the other returns the created UserProfile. Though it seems that User + UserProfile belong to the same bounded context, the fact that they reside in two different microservices may indicate they are not (in this case I think the first microservice really is for Authentication and the other for UserProfiles which indicate different bounded contexts to me). Best-practice is to have a microservice implement their own encapsulated bounded context.
(Note: Answered an old question in hopes it is informative to others)