i want to implement canActivate() method so to restrict admin routes. I'm returning observable of type user in checkAdmin method (in my service file) but canActivate method's return type is observable of type boolean. So i'm unable to convert observable of type "User" to observable of boolean. Please help me as i'm new in angular and mean stack
user schema model
/* ===================
Import Node Modules
=================== */
const mongoose = require('mongoose'); // Node Tool for MongoDB
mongoose.Promise = global.Promise; // Configure Mongoose Promises
const Schema = mongoose.Schema; // Import Schema from Mongoose
const bcrypt = require('bcrypt-nodejs'); // A native JS bcrypt library for NodeJS
// Validate Function to check e-mail length
let emailLengthChecker = (email) => {
// Check if e-mail exists
if (!email) {
return false; // Return error
} else {
// Check the length of e-mail string
if (email.length < 5 || email.length > 30) {
return false; // Return error if not within proper length
} else {
return true; // Return as valid e-mail
}
}
};
// Validate Function to check if valid e-mail format
let validEmailChecker = (email) => {
// Check if e-mail exists
if (!email) {
return false; // Return error
} else {
// Regular expression to test for a valid e-mail
const regExp = new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
return regExp.test(email); // Return regular expression test results (true or false)
}
};
// Array of Email Validators
const emailValidators = [
// First Email Validator
{
validator: emailLengthChecker,
message: 'E-mail must be at least 5 characters but no more than 30'
},
// Second Email Validator
{
validator: validEmailChecker,
message: 'Must be a valid e-mail'
}
];
// Validate Function to check username length
let usernameLengthChecker = (username) => {
// Check if username exists
if (!username) {
return false; // Return error
} else {
// Check length of username string
if (username.length < 3 || username.length > 15) {
return false; // Return error if does not meet length requirement
} else {
return true; // Return as valid username
}
}
};
// Validate Function to check if valid username format
let validUsername = (username) => {
// Check if username exists
if (!username) {
return false; // Return error
} else {
// Regular expression to test if username format is valid
const regExp = new RegExp(/^[a-zA-Z0-9]+$/);
return regExp.test(username); // Return regular expression test result (true or false)
}
};
// Array of Username validators
const usernameValidators = [
// First Username validator
{
validator: usernameLengthChecker,
message: 'Username must be at least 3 characters but no more than 15'
},
// Second username validator
{
validator: validUsername,
message: 'Username must not have any special characters'
}
];
// Validate Function to check password length
let passwordLengthChecker = (password) => {
// Check if password exists
if (!password) {
return false; // Return error
} else {
// Check password length
if (password.length < 8 || password.length > 35) {
return false; // Return error if passord length requirement is not met
} else {
return true; // Return password as valid
}
}
};
// Validate Function to check if valid password format
let validPassword = (password) => {
// Check if password exists
if (!password) {
return false; // Return error
} else {
// Regular Expression to test if password is valid format
const regExp = new RegExp(/^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?[\d])(?=.*?[\W]).{8,35}$/);
return regExp.test(password); // Return regular expression test result (true or false)
}
};
// Array of Password validators
const passwordValidators = [
// First password validator
{
validator: passwordLengthChecker,
message: 'Password must be at least 8 characters but no more than 35'
},
// Second password validator
{
validator: validPassword,
message: 'Must have at least one uppercase, lowercase, special character, and number'
}
];
// User Model Definition
const userSchema = new Schema({
email: { type: String, required: true, unique: true, lowercase: true, validate: emailValidators },
username: { type: String, required: true, unique: true, lowercase: true, validate: usernameValidators },
password: { type: String, required: true, validate: passwordValidators },
isAdmin: { type: Boolean, default: false }
});
// Schema Middleware to Encrypt Password
userSchema.pre('save', function(next) {
// Ensure password is new or modified before applying encryption
if (!this.isModified('password'))
return next();
// Apply encryption
bcrypt.hash(this.password, null, null, (err, hash) => {
if (err) return next(err); // Ensure no errors
this.password = hash; // Apply encryption to password
next(); // Exit middleware
});
});
// Methods to compare password to encrypted password upon login
userSchema.methods.comparePassword = function(password) {
return bcrypt.compareSync(password, this.password); // Return comparison of login password to password in database (true or false)
};
// Export Module/Schema
module.exports = mongoose.model('User', userSchema);
http routes which contains /profile route to get the user from mongodb database
const User = require('../models/user'); // Import User Model Schema
const jwt = require('jsonwebtoken');
const config = require('../config/database');
module.exports = (router) => {
/* ==============
Register Route
============== */
router.post('/register', (req, res) => {
// Check if email was provided
if (!req.body.email) {
res.json({ success: false, message: 'You must provide an e-mail' }); // Return error
} else {
// Check if username was provided
if (!req.body.username) {
res.json({ success: false, message: 'You must provide a username' }); // Return error
} else {
// Check if password was provided
if (!req.body.password) {
res.json({ success: false, message: 'You must provide a password' }); // Return error
} else {
// Create new user object and apply user input
let user = new User({
email: req.body.email.toLowerCase(),
username: req.body.username.toLowerCase(),
password: req.body.password
});
// Save user to database
user.save((err) => {
// Check if error occured
if (err) {
// Check if error is an error indicating duplicate account
if (err.code === 11000) {
res.json({ success: false, message: 'Username or e-mail already exists' }); // Return error
} else {
// Check if error is a validation rror
if (err.errors) {
// Check if validation error is in the email field
if (err.errors.email) {
res.json({ success: false, message: err.errors.email.message }); // Return error
} else {
// Check if validation error is in the username field
if (err.errors.username) {
res.json({ success: false, message: err.errors.username.message }); // Return error
} else {
// Check if validation error is in the password field
if (err.errors.password) {
res.json({ success: false, message: err.errors.password.message }); // Return error
} else {
res.json({ success: false, message: err }); // Return any other error not already covered
}
}
}
} else {
res.json({ success: false, message: 'Could not save user. Error: ', err }); // Return error if not related to validation
}
}
} else {
res.json({ success: true, message: 'Acount registered!' }); // Return success
}
});
}
}
}
});
router.get('/checkEmail/:email', (req, res) => {
if (!req.params.email) {
res.json({ success: false, message: 'email not provided'});
} else {
User.findOne({ email: req.params.email}, (err, user) => {
if (err) {
res.json({ success: false, message: err});
} else {
if (user) {
res.json({ success: false, message: 'email taken'});
} else {
res.json({ success: true, message: 'email available'});
}
}
});
}
});
router.get('/checkUsername/:username', (req, res) => {
if (!req.params.username) {
res.json({ success: false, message: 'username not provided'});
} else {
User.findOne({ username: req.params.username}, (err, user) => {
if (err) {
res.json({ success: false, message: err});
} else {
if (user) {
res.json({ success: false, message: 'username taken'});
} else {
res.json({ success: true, message: 'username available'});
}
}
});
}
});
router.post('/login', (req, res) => {
if (!req.body.username) {
res.json({ success: false, message: 'No username was provided'});
} else {
if (!req.body.password) {
res.json({ success: false, message: 'No password was provided'});
} else {
User.findOne({ username: req.body.username.toLowerCase() }, (err, user) => {
if (err) {
res.json({ success: false, message: err});
} else {
if (!user) {
res.json({ success: false, message: 'No user exist'});
} else {
const validPassword = user.comparePassword(req.body.password);
if (!validPassword) {
res.json({ success: false, message: 'password invalid'});
} else {
const token = jwt.sign({userId: user._id}, config.secret, {expiresIn: '24h'});
res.json({ success: true, message: 'Success!', token: token, user: {username: user.username}});
}
}
}
});
}
}
});
// MIDDLEWARE TO INTERCEPT HEADERS
// THIS MIDDLEWARE DECRYPTS THE TOKEN
router.use((req, res, next) => {
const token = req.headers['authorization']; // whenever a request coming from angular2 with headers attached it is going to search fot this header
if (!token) {
res.json({ success: false, message: 'No token provided'});
} else {
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
res.json({ success: false, message: 'invalid token' + err});
} else {
req.decoded = decoded;
next();
}
});
}
})
// ANY ROUTES COMING AFTER THIS MIDDLEWARE WILL PASS THROUGH THE SAME
// BELOW METHOD TAKES THE DECRYPTED TOKEN FIND THE USER
router.get('/profile', (req, res) => {
User.findOne({ _id: req.decoded.userId }).select('username email').exec((err, user) => {
if (err) {
res.json({ success: false, message: err});
} else {
if (!user) {
res.json({ success: false, message: 'user not found'});
} else {
res.json({ success: true, user: user });
}
}
});
});
return router; // Return router object to main index.js
}
this the service file which call the http method to get the user
import { Injectable } from '@angular/core';
import 'rxjs/add/operator/map';
import { Http, Headers, RequestOptions } from '@angular/http';
// import { map } from "rxjs/operators";
// import { map } from 'rxjs/operators';
import { switchMap } from 'rxjs/operators';
import { tokenNotExpired } from 'angular2-jwt';
import { User } from '../shared/user';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
@Injectable()
export class AuthService {
domain = 'http://localhost:3000';
authToken;
user;
options;
constructor(private http: Http) { }
registerUser(user) {
return this.http.post(this.domain + '/authentication/register', user).map(res => res.json());
}
createAuthenticationHeaders() {
this.loadToken();
this.options = new RequestOptions({
headers : new Headers({
'Content-Type': 'application/json',
'authorization': this.authToken
})
});
}
loadToken() {
this.authToken = localStorage.getItem('token');
}
checkUsername(username) {
return this.http.get(this.domain + '/authentication/checkUsername/' + username).map(res => res.json());
}
checkEmail(email) {
return this.http.get(this.domain + '/authentication/checkEmail/' + email).map(res => res.json());
}
login(user) {
return this.http.post(this.domain + '/authentication/login', user).map(res => res.json());
}
logout() {
this.authToken = null;
this.user = null;
localStorage.clear();
}
storeUserData(token, user) {
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));
this.authToken = token;
this.user = user;
}
getProfile() {
this.createAuthenticationHeaders();
return this.http.get(this.domain + '/authentication/profile', this.options).map(res => res.json());
}
checkAdmin(): Observable<User> {
this.createAuthenticationHeaders();
return this.http.get(this.domain + '/authentication/profile', this.options).map(res => res.json());
}
loggedIn() {
return tokenNotExpired();
}
}
this is the admin auth guard file in which i'm having problem in canActivate() method to convert observable of type user to observable of boolean
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRoute, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../services/auth.service';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
import { User } from '../shared/user';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class AdminAuthGuard implements CanActivate {
redirectUrl;
constructor(private authService: AuthService, private router: Router) {
}
canActivate(router: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
/*return this.authService.user.switchMap(user => this.authService.getProfile1())
.map(user => user.isAdmin);*/
const subject = new Subject();
// get user access levels
return this.authService.checkAdmin()
.map(user => {
const isAdmin = user.isAdmin;
if (isAdmin === true ) {
return true;
}
return false;
});
}
}
Application user interface
export interface User {
username: string;
email: string;
isAdmin: boolean;
}