Profile model working

This commit is contained in:
Fergal Moran
2018-04-22 19:36:08 +01:00
parent 6da5c22904
commit 07eced07ff
16 changed files with 286 additions and 39 deletions

View File

@@ -11,6 +11,7 @@
"assets",
"favicon.ico",
"firebase-messaging-sw.js",
"facebook-auth.html",
"manifest.json"
],
"index": "index.html",

View File

@@ -1,6 +1,6 @@
import { PodnomsAuthService } from './../../services/podnoms-auth.service';
import { AuthService } from 'angularx-social-login';
import { AuthService, LoginOpt } from 'angularx-social-login';
import {
FacebookLoginProvider,
GoogleLoginProvider,
@@ -16,11 +16,9 @@ import { Subscription } from 'rxjs/Subscription';
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
private _authWindow: Window;
private _subscription: Subscription;
brandNew: boolean = false;
user: any;
username: string;
password: string;
isRequesting: boolean = false;
@@ -33,7 +31,6 @@ export class LoginComponent implements OnInit {
private _activatedRoute: ActivatedRoute,
private _router: Router
) {}
ngOnInit() {
this._subscription = this._activatedRoute.queryParams.subscribe(
(param: any) => {
@@ -45,7 +42,14 @@ export class LoginComponent implements OnInit {
login(provider?: string) {
this.isRequesting = true;
if (provider === 'facebook') {
this._socialAuthService.signIn(FacebookLoginProvider.PROVIDER_ID);
const options: LoginOpt = {
scope: 'email public_profile',
redirect_uri: 'http://localhost:5000/facebook-auth.html'
};
this._socialAuthService.signIn(
FacebookLoginProvider.PROVIDER_ID,
options
);
} else {
this._authService
.login(this.username, this.password)
@@ -58,7 +62,19 @@ export class LoginComponent implements OnInit {
}
this._socialAuthService.authState.subscribe((user) => {
this.user = user;
this._authService
.facebookLogin(user.authToken)
.finally(() => (this.isRequesting = false))
.subscribe(
(result) => {
if (result) {
this._router.navigate(['/']);
}
},
(error) => {
this.errorMessage = error;
}
);
});
}
logout() {}

View File

@@ -0,0 +1,36 @@
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpHeaderResponse,
HttpHeaders
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class PodNomsApiInterceptor implements HttpInterceptor {
private commonHeaders(): HttpHeaders {
const headers = new HttpHeaders({
'cache-control': 'no-cache',
'content-type': 'application/json'
});
return headers;
}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const authToken = localStorage.getItem('auth_token');
let headers = this.commonHeaders();
if (authToken) {
headers = headers.append('Authorization', `Bearer ${authToken}`);
}
const changedReq = req.clone({
headers: headers
});
return next.handle(changedReq);
}
}

View File

@@ -1,10 +1,12 @@
export class ProfileModel {
id?: number;
id?: string;
slug: string;
email: string;
name: string;
description?: string;
uid?: string;
apiKey?: string;
profileImage?: string;
apiKey: string;
firstName: string;
lastName: string;
}

View File

@@ -0,0 +1,27 @@
import { Observable } from 'rxjs/Rx';
export abstract class BaseService {
constructor() {}
protected handleError(error: any) {
const applicationError = error.headers.get('Application-Error');
// either applicationError in header or model error in body
if (applicationError) {
return Observable.throw(applicationError);
}
let modelStateErrors: string = '';
const serverError = error.json();
if (!serverError.type) {
for (let key in serverError) {
if (serverError[key])
modelStateErrors += serverError[key] + '\n';
}
}
modelStateErrors = modelStateErrors = '' ? null : modelStateErrors;
return Observable.throw(modelStateErrors || 'Server error');
}
}

View File

@@ -0,0 +1,108 @@
import { environment } from 'environments/environment';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/filter';
import { Headers, RequestOptions } from '@angular/http';
import { BaseService } from './base.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { ProfileModel } from '../models/profile.model';
@Injectable()
export class PodnomsAuthService extends BaseService {
private _authNavStatusSource = new BehaviorSubject<boolean>(false);
authNavStatus$ = this._authNavStatusSource.asObservable();
private _loggedIn = false;
user: ProfileModel;
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
constructor(private _router: Router, private _http: HttpClient) {
super();
this._loggedIn = !!localStorage.getItem('auth_token');
// ?? not sure if this the best way to broadcast the status but seems to resolve issue on page refresh where auth status is lost in
// header component resulting in authed user nav links disappearing despite the fact user is still logged in
this._authNavStatusSource.next(this._loggedIn);
}
setUser(user: ProfileModel) {
this.user = user;
}
getUser() {
return this.user;
}
isAuthenticated() {
return this._loggedIn;
}
getToken() {
return localStorage.getItem('auth_token');
}
login(userName, password) {
return this._http
.post(
environment.API_HOST + '/auth/login',
JSON.stringify({ userName, password }),
this.httpOptions
)
.map((res) => {
localStorage.setItem('auth_token', res['auth_token']);
this._loggedIn = true;
this._authNavStatusSource.next(true);
return true;
})
.catch(this.handleError);
}
facebookLogin(accessToken: string) {
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
const body = JSON.stringify({ accessToken });
return this._http
.post<string>(
environment.API_HOST + '/externalauth/facebook',
body,
{
headers
}
)
.map((res) => {
localStorage.setItem('auth_token', res['auth_token']);
this._loggedIn = true;
this._authNavStatusSource.next(true);
return true;
})
.catch(this.handleError);
}
public signup(email: string, password: string): Observable<ProfileModel> {
let body = JSON.stringify({
email,
password
});
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this._http
.post<ProfileModel>(
environment.API_HOST + '/accounts',
body,
this.httpOptions
)
.map((res) => true)
.catch(this.handleError);
}
public logout() {
localStorage.removeItem('auth_token');
this._router.navigate(['/']);
}
public resetPassword(userName: string) {}
public loginSocial(provider: string): void {}
}

View File

@@ -0,0 +1,9 @@
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&#]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}

View File

@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>JwtAuthDemo - Facebook Auth</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="assets/util.js"></script>
</head>
<body>
<script>
// if we don't receive an access token then login failed and/or the user has not connected properly
var accessToken = getParameterByName("access_token");
var message = {};
message.type = 'fbcallback';
if (accessToken) {
message.status = true;
message.accessToken = accessToken;
}
else {
message.status = false;
message.error = getParameterByName("error");
message.errorDescription = getParameterByName("error_description");
}
window.opener.postMessage(JSON.stringify(message), "http://localhost:4200");
</script>
</body>
</html>

View File

@@ -30,7 +30,7 @@ namespace PodNoms.Api.Controllers {
// var result = await _userRepository.AddOrUpdate(userIdentity, model.Password);
if (!result.Succeeded) return new BadRequestObjectResult(result);
return new OkObjectResult(model);
return new OkObjectResult(model );
}
}
}

View File

@@ -8,6 +8,7 @@ using AutoMapper;
using Hangfire;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using PodNoms.Api.Models;
@@ -23,17 +24,19 @@ namespace PodNoms.Api.Controllers {
public class PodcastController : Controller {
private readonly IPodcastRepository _repository;
private readonly IUserRepository _userRepository;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IOptions<AppSettings> _settings;
private readonly IMapper _mapper;
private ClaimsPrincipal _caller;
private readonly IUnitOfWork _uow;
public PodcastController(IPodcastRepository repository, IUserRepository userRepository,
public PodcastController(IPodcastRepository repository, IUserRepository userRepository, UserManager<ApplicationUser> userManager,
IOptions<AppSettings> options, IMapper mapper, IUnitOfWork unitOfWork, IHttpContextAccessor httpContextAccessor) {
_caller = httpContextAccessor.HttpContext.User;
this._caller = httpContextAccessor.HttpContext.User;
this._uow = unitOfWork;
this._repository = repository;
this._userRepository = userRepository;
this._userManager = userManager;
this._settings = options;
this._mapper = mapper;
}
@@ -62,28 +65,25 @@ namespace PodNoms.Api.Controllers {
[HttpPost]
public async Task<IActionResult> Post([FromBody] PodcastViewModel vm) {
var userId = _caller.Claims.Single(c => c.Type == "id");
var user = _userRepository.Get(userId.Value);
if (user == null)
return new BadRequestObjectResult("Unable to look up user profile");
var user = await this._userManager.FindByIdAsync(userId.Value);
if (user != null) {
if (ModelState.IsValid) {
var item = _mapper.Map<PodcastViewModel, Podcast>(vm);
if (ModelState.IsValid) {
var item = _mapper.Map<PodcastViewModel, Podcast>(vm);
item.User = user;
//remove once we're ready
item.User = _userRepository.Get("fergal.moran@gmail.com");
item.AppUser = user;
var ret = await _repository.AddOrUpdateAsync(item);
await _uow.CompleteAsync();
return new OkObjectResult(_mapper.Map<Podcast, PodcastViewModel>(ret));
var ret = await _repository.AddOrUpdateAsync(item);
await _uow.CompleteAsync();
return new OkObjectResult(_mapper.Map<Podcast, PodcastViewModel>(ret));
}
}
return BadRequest("Invalid request data");
}
[HttpPut]
public async Task<IActionResult> Put([FromBody] PodcastViewModel vm) {
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
var user = _userRepository.Get(email);
if (string.IsNullOrEmpty(email) || user == null)
return new BadRequestObjectResult("Unable to look up user profile");
if (ModelState.IsValid) {
var podcast = await _repository.GetAsync(vm.Id);
if (podcast != null) {

View File

@@ -3,43 +3,50 @@ using System.Security.Claims;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using PodNoms.Api.Models;
using PodNoms.Api.Models.ViewModels;
using PodNoms.Api.Persistence;
using PodNoms.Api.Services.Auth;
namespace PodNoms.Api.Controllers {
[Authorize]
[Route("[controller]")]
public class ProfileController : Controller {
private IUserRepository _userRepository;
private readonly UserManager<ApplicationUser> _userManager;
public IUnitOfWork _unitOfWork { get; }
private readonly ClaimsPrincipal _caller;
public IMapper _mapper { get; }
public ProfileController(IUserRepository userRepository, IMapper mapper, IUnitOfWork unitOfWork) {
public ProfileController(IUserRepository userRepository, IMapper mapper, IUnitOfWork unitOfWork,
UserManager<ApplicationUser> userManager, IHttpContextAccessor httpContextAccessor) {
this._caller = httpContextAccessor.HttpContext.User;
this._mapper = mapper;
this._unitOfWork = unitOfWork;
this._userManager = userManager;
this._userRepository = userRepository;
}
[HttpGet]
public async Task<ActionResult<ProfileViewModel>> Get() {
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
var user = await _userRepository.GetAsync(email);
if (user != null) {
var result = _mapper.Map<User, ProfileViewModel>(user);
return new OkObjectResult(result);
}
return new NotFoundResult();
var userId = _caller.Claims.Single(c => c.Type == "id");
var user = await this._userManager.FindByIdAsync(userId.Value);
var result = _mapper.Map<ApplicationUser, ProfileViewModel>(user);
return new OkObjectResult(result);
}
[HttpPost]
public async Task<ActionResult> Post([FromBody] ProfileViewModel item) {
/* TODO: Update this to the new model
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
var user = _userRepository.Get(email);
if (user == null || user.Id != item.Id)
return new UnauthorizedResult();
user.Id = item.Id;
user.EmailAddress = item.Email;
user.FullName = item.Name;
@@ -48,6 +55,7 @@ namespace PodNoms.Api.Controllers {
_userRepository.AddOrUpdate(user);
await _unitOfWork.CompleteAsync();
*/
return new OkObjectResult(item);
}

View File

@@ -1,6 +1,6 @@
namespace PodNoms.Api.Models.ViewModels {
public class ProfileViewModel {
public int Id { get; set; }
public string Id { get; set; }
public string Slug { get; set; }
public string Email { get; set; }
public string Name { get; set; }

View File

@@ -4,7 +4,6 @@ namespace PodNoms.Api.Models.ViewModels {
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
}
}

View File

@@ -3,7 +3,8 @@ using PodNoms.Api.Models;
namespace PodNoms.Api.Persistence {
public interface IUserRepository {
User Get(string id);
User Get(int id);
User Get(string email);
Task<User> GetAsync(string id);
Task<User> GetBySlugAsync(string slug);
User UpdateRegistration(string email, string name, string sid, string providerId, string profileImage, string refreshToken);

View File

@@ -33,6 +33,14 @@ namespace PodNoms.Api.Providers {
src => src.Name,
e => e.MapFrom(m => m.FullName));
CreateMap<ApplicationUser, ProfileViewModel>()
.ForMember(
src => src.Name,
map => map.MapFrom(s => $"{s.FirstName} {s.LastName}"))
.ForMember(
src => src.ProfileImage,
map => map.MapFrom(s => s.PictureUrl));
//API Resource to Domain
CreateMap<PodcastViewModel, Podcast>()
.ForMember(v => v.ImageUrl, map => map.Ignore())

View File

@@ -97,6 +97,7 @@ namespace PodNoms.Api {
services.Configure<StorageSettings>(Configuration.GetSection("Storage"));
services.Configure<ApplicationsSettings>(Configuration.GetSection("ApplicationsSettings"));
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
services.Configure<FacebookAuthSettings>(Configuration.GetSection("FacebookAuthSettings"));
services.Configure<ImageFileStorageSettings>(Configuration.GetSection("ImageFileStorageSettings"));
services.Configure<AudioFileStorageSettings>(Configuration.GetSection("AudioFileStorageSettings"));
services.Configure<FormOptions>(options => {