Making an http call inside of switchmap - rxjs

I am writing an angular app with firebase auth and a dotnet core backend. I am trying to create a service so that i can track the firebase uid and track if the user is an admin (which is obtained from the backend server). I can successfulyl get the uid from firebase with no issue, but when i try to get the user object from my api I get errors in the console.
Here is my code in my user service:
export class UserService {
uid = this.afAuth.authState.pipe(
map(authState => {
return !authState ? null : authState.uid;
})
);
isAdmin = this.uid.pipe(
switchMap(uid => {
if (uid) {
console.log("there is a uid")
this.httpClient.get<User>("https://localhost:44337/api/users/" + uid).subscribe(data => {
console.log(data.isAdmin); // prints 'true'
return observableOf(data.isAdmin);
});
} else {
return observableOf(false);
}
})
);
constructor(
private afAuth: AngularFireAuth,
private httpClient: HttpClient) { }
}

Inside a switchMap operator you should not directly subscribe to an observable. You should return the observable and the switchMap operator will handle the subscription for you.
switchMap(uid => {
if (uid) {
console.log("there is a uid")
return this.httpClient.get<User>("https://localhost:44337/api/users/" + uid).pipe(map(data => {
console.log(data.isAdmin); // prints 'true'
return data.isAdmin
}));
} else {
return of(false);
}
})

Related

Supabase third party oAuth providers are returning null?

I'm trying to implement Facebook, Google and Twitter authentication. So far, I've set up the apps within the respective developer platforms, added those keys/secrets to my Supabase console, and created this graphql resolver:
/* eslint-disable #typescript-eslint/explicit-module-boundary-types */
import camelcaseKeys from 'camelcase-keys';
import { supabase } from 'lib/supabaseClient';
import { LoginInput, Provider } from 'generated/types';
import { Provider as SupabaseProvider } from '#supabase/supabase-js';
import Context from '../../context';
import { User } from '#supabase/supabase-js';
export default async function login(
_: any,
{ input }: { input: LoginInput },
{ res, req }: Context
): Promise<any> {
const { provider } = input;
// base level error object
const errorObject = {
__typename: 'AuthError',
};
// return error object if no provider is given
if (!provider) {
return {
...errorObject,
message: 'Must include provider',
};
}
try {
const { user, session, error } = await supabase.auth.signIn({
// provider can be 'github', 'google', 'gitlab', or 'bitbucket'
provider: 'facebook',
});
console.log({ user });
console.log({ session });
console.log({ error });
if (error) {
return {
...errorObject,
message: error.message,
};
}
const response = camelcaseKeys(user as User, { deep: true });
return {
__typename: 'LoginSuccess',
accessToken: session?.access_token,
refreshToken: session?.refresh_token,
...response,
};
} catch (error) {
return {
...errorObject,
message: error.message,
};
}
}
I have three console logs set up directly underneath the signIn() function, all of which are returning null.
I can also go directly to https://<your-ref>.supabase.co/auth/v1/authorize?provider=<provider> and auth works correctly, so it appears to have been narrowed down specifically to the signIn() function. What would cause the response to return null values?
This is happening because these values are not populated until after the redirect from the OAuth server takes place. If you look at the internal code of supabase/gotrue-js you'll see null being returned explicitly.
private _handleProviderSignIn(
provider: Provider,
options: {
redirectTo?: string
scopes?: string
} = {}
) {
const url: string = this.api.getUrlForProvider(provider, {
redirectTo: options.redirectTo,
scopes: options.scopes,
})
try {
// try to open on the browser
if (isBrowser()) {
window.location.href = url
}
return { provider, url, data: null, session: null, user: null, error: null }
} catch (error) {
// fallback to returning the URL
if (!!url) return { provider, url, data: null, session: null, user: null, error: null }
return { data: null, user: null, session: null, error }
}
}
The flow is something like this:
Call `supabase.auth.signIn({ provider: 'github' })
User is sent to Github.com where they will be prompted to allow/deny your app access to their data
If they allow your app access, Github.com redirects back to your app
Now, through some Supabase magic, you will have access to the session, user, etc. data

Nested dispatch function does not get update props

app.js
const mapStateToProps = (state) => {
return {home:state}
}
const mapDispatchToProps = (dispatch) => {
return {
guestLogin: (data)=>{dispatch(guestLogin(data)).then(()=>{
dispatch(initiateTrans(stateProps.home))
})},
};
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return Object.assign({}, ownProps, stateProps, dispatchProps,{
initiateTrans: () => dispatchProps.initiateTrans(stateProps.home),
})
}
Action.js
export const guestLogin= (state)=>{
var data={
'email':state.email,
'name':state.name,
'phone_number':state.ph_number,
'phone_code':state.country_code
}
return function(dispatch) {
return dataservice.guestSignup(data).then(res => {
dispatch(afterLoggedGuest(res))
}).catch(error => {
throw(error);
});
}
}
function afterLoggedGuest(result) {
return {type: guestLoginChange, result};
}
export const initiateTrans= (updatedState)=>{
return function(dispatch) {
return dataservice.initiateTransaction(updatedState).then(res => {
console.log("initiateTransaction",res)
}).catch(error => {
throw(error);
});
}
}
Reducer.js
if(action.type === guestLoginChange){
return {
...state,guestData: {
...state.guestData,
Authorization: action.result.authentication ,
auth_token: action.result.auth_token ,
platform: action.result.platform
} ,
}
}
I am having two api requests.. After first api request success i want to update state value then pass that updated state to another api request..
I tried to get the updted props
how to dispatch the initiateTrans with update props
I need to update value at api request success in call back i need to call one more request with updated state value
currently i am not able to get the update props value
I think this is a good use case for thunk (redux-thunk), which is a middleware that allows you to execute multiple dispatches in an action.
You will need to apply the middleware when you configure the initial store (see docs on link above). But then in your actions, you can wrap the code with a dispatch return statement, which gives you access to multiple calls. For example:
export const guestLogin= (state)=>{
return dispatch => {
var data={...} // some data in here
return dataservice.guestSignup(data).then(res => {
dispatch(afterLoggedGuest(res))
}).catch(error => {
throw(error);
// could dispatch here as well...
});
}
}

Apollo Server - Apply Authentication to Certain Resolvers Only with Passport-JWT

I currently have a Node.js back-end running Express with Passport.js for authentication and am attempting to switch to GraphQL with Apollo Server. My goal is to implement the same authentication I am using currently, but cannot figure out how to leave certain resolvers public while enabling authorization for others. (I have tried researching this question extensively yet have not been able to find a suitable solution thus far.)
Here is my code as it currently stands:
My JWT Strategy:
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = JWT_SECRET;
module.exports = passport => {
passport.use(
new JwtStrategy(opts, async (payload, done) => {
try {
const user = await UserModel.findById(payload.sub);
if (!user) {
return done(null, false, { message: "User does not exist!" });
}
done(null, user);
} catch (error) {
done(err, false);
}
})
);
}
My server.js and Apollo configuration:
(I am currently extracting the bearer token from the HTTP headers and passing it along to my resolvers using the context object):
const apollo = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
let authToken = "";
try {
if (req.headers.authorization) {
authToken = req.headers.authorization.split(" ")[1];
}
} catch (e) {
console.error("Could not fetch user info", e);
}
return {
authToken
};
}
});
apollo.applyMiddleware({ app });
And finally, my resolvers:
exports.resolvers = {
Query: {
hello() {
return "Hello world!";
},
async getUserInfo(root, args, context) {
try {
const { id } = args;
let user = await UserModel.findById(id);
return user;
} catch (error) {
return "null";
}
},
async events() {
try {
const eventsList = await EventModel.find({});
return eventsList;
} catch (e) {
return [];
}
}
}
};
My goal is to leave certain queries such as the first one ("hello") public while restricting the others to requests with valid bearer tokens only. However, I am not sure how to implement this authorization in the resolvers using Passport.js and Passport-JWT specifically (it is generally done by adding middleware to certain endpoints, however since I would only have one endpoint (/graphql) in this example, that option would restrict all queries to authenticated users only which is not what I am looking for. I have to perform the authorization in the resolvers somehow, yet not sure how to do this with the tools available in Passport.js.)
Any advice is greatly appreciated!
I would create a schema directive to authorized query on field definition and then use that directive wherever I want to apply authorization. Sample code :
class authDirective extends SchemaDirectiveVisitor {
visitObject(type) {
this.ensureFieldsWrapped(type);
type._requiredAuthRole = this.args.requires;
}
visitFieldDefinition(field, details) {
this.ensureFieldsWrapped(details.objectType);
field._requiredAuthRole = this.args.requires;
}
ensureFieldsWrapped(objectType) {
// Mark the GraphQLObjectType object to avoid re-wrapping:
if (objectType._authFieldsWrapped) return;
objectType._authFieldsWrapped = true;
const fields = objectType.getFields();
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName];
const {
resolve = defaultFieldResolver
} = field;
field.resolve = async function (...args) {
// your authorization code
return resolve.apply(this, args);
};
});
}
}
And declare this in type definition
directive #authorization(requires: String) on OBJECT | FIELD_DEFINITION
map schema directive in your schema
....
resolvers,
schemaDirectives: {
authorization: authDirective
}
Then use it on your api end point or any object
Query: {
hello { ... }
getuserInfo():Result #authorization(requires:authToken) {...}
events():EventResult #authorization(requires:authToken) {...}
};

Call multiple ajax and wait for result Angular2

I have problem with my Angular. I have this functions:
private callUserInfo(): any {
this.isLoading = true;
return this._ajaxService.getService('/system/ping')
.map(
result => {
this.userId =
result.participant.substring(result.participant.indexOf('#'));
this.isLoading = false;
}
)
.catch(error => {
return Observable.throw(error);
});
}
public loadUserData(userName: string): any {
this.isLoading = true;
return this._ajaxService.getService('/User/' + userName)
.map(
result => {
const data = result[0];
this.user = new User(
data.id,
data.contacts[0].email,
data.name,
data.surname,
data.address.street,
data.address.city,
data.address.state,
data.address.country,
data.address.postCode,
data.address.timeZone);
this.isLoading = false;
})
.catch(error => {
return Observable.throw(error);
});
}
public getUser(): any {
if (this.user == null) {
this.callUserInfo().subscribe(() => {
this.loadUserData(this.userId).subscribe(() => {
return this.user;
});
});
} else {
return this.user;
}
}
In my component I call this service functions like this (auth service is service with functions defined up):
constructor(private _auth: AuthService) {
this.user = _auth.getUser();
}
But it stills return null (because Ajax calls are not finished?) Can someone explain me, how to call this two calls (first is system/ping service and based on return (userId) I need to call second ajax call (/user/id). After this two calls I have defined user in my service and I can return it to other components. Can someone expllain me, what am i doing wrong, or how I can do it better? I´m using newest version of angular.
P.S. Get service is from my wrapper service:
getService(url: string): Observable<any> {
return this.http
.get(this.base + url, this.options)
.map(this.extractData)
.catch(this.handleError);
}
You are not returning anything in case this.user==null
Change your function as following:
userObservabel=new BehaviourSubject(null);
public getUser(): any {
if (this.user == null) {
this.callUserInfo().subscribe(() => {
this.loadUserData(this.userId).subscribe(() => {
this.userObservabel.next(this.user);
});
});
return this.userObservabel.asObservable();
} else {
return this.userObservabel.asObservable();
}
}
and then you need to subscribe it
constructor(private _auth: AuthService) {
_auth.getUser().subscribe(user => this.user = user);
}
You need to call the second service in the subscribe or in the map method i.e. the Observable has returned a promise and that is resolved. Once that is resolved u should call your chained service.
A sample snipped from my POC might help you
this._accountListService.getAccountsFromBE().subscribe(
response => {
this.response = response;
this._accountListService.getAccountSorting().subscribe(
response => {
this.acctSort = response;
if (response.prodCode) {
this._accountListService.getAccountOrder().subscribe(
response => {
this.acctOrder = response;
this.response = this.setAccountOrder(this.response);
this.response.sort(this.myComparator);
this.acctFlag = true;
if (this.prodDesc) {
this.loader = false;
this.accountDetl = this.response[0];
this.accountDetl.entCdeDesc = this.prodDesc[this.accountDetl.entProdCatCde];
}
},
err => console.log(err)
);
}
},
err => console.log(err)
);
},
err => console.log(err)
);

How to observe the angular 5 interceptor error in some component

Hi I am new to angular 5 and followed some blogs to write the HTTP Interceptor.
export class AngularInterceptor implements HttpInterceptor {
public http404 = false;
constructor() { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log("intercepted request ... ");
// Clone the request to add the new header.
const httpReq = req.clone(
{
headers: req.headers.set("headerName", "headerValue")
}
);
console.log("Sending request with new header now ...");
//send the newly created request
return next.handle(httpReq)
.catch((error, caught) => {
//intercept the respons error and displace it to the console
console.log("Error Occurred");
if(error.status === 404)
this.http404 = true;
//need to pass this value to another component. Let's say app.component.ts and display some message to the user.
//return the error to the method that called it
return Observable.throw(error);
}) as any;
}
}
This is working fine. But what I need to do is to pass this error code to other components and print out a message on the screen for the user. One wy to do that is to create an observable but I am unable to implement that.
Any help is highly appreciated.
You can use a service to do that, by leveraging a Subject. Here's an example of using BehaviourSubject.
First you create a service. This service will be shared across the two classes:
export class BroadcastService {
public http404: BehaviorSubject<boolean>;
constructor() {
//initialize it to false
this.http404 = new BehaviorSubject<boolean>(false);
}
}
In your HttpInterceptor class, you inject the BroadcastService into it. To update the BehvaiourSubject, simply use .next():
export class AngularInterceptor implements HttpInterceptor {
public http404 = false;
constructor(public broadcastService: BroadcastService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log("intercepted request ... ");
// Clone the request to add the new header.
const httpReq = req.clone({
headers: req.headers.set("headerName", "headerValue")
});
console.log("Sending request with new header now ...");
//send the newly created request
return next.handle(httpReq)
.catch((error, caught) => {
//intercept the respons error and displace it to the console
console.log("Error Occurred");
if (error.status === 404)
this.http404 = true;
//need to pass this value to another component. Let's say app.component.ts and display some message to the user.
this.broadcastService.http404.next(true);
//return the error to the method that called it
return Observable.throw(error);
}) as any;
}
}
And in your app.component.ts, simply subscribe it using .asObservable(). You need to inject it too:
export class AppComponent implements ngOnInit {
constructor(public broadCastService: BroadcastService) {
}
OnInit() {
this.broadCastService.http404.asObservable().subscribe(values => {
console.log(values); // will return false if http error
});
}
}

Resources