mirror of
https://github.com/fergalmoran/podnoms.git
synced 2025-12-22 09:18:08 +00:00
Profile model working
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
"assets",
|
||||
"favicon.ico",
|
||||
"firebase-messaging-sw.js",
|
||||
"facebook-auth.html",
|
||||
"manifest.json"
|
||||
],
|
||||
"index": "index.html",
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
36
client/src/app/interceptors/podnoms-api.interceptor.ts
Normal file
36
client/src/app/interceptors/podnoms-api.interceptor.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
27
client/src/app/services/base.service.ts
Normal file
27
client/src/app/services/base.service.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
108
client/src/app/services/podnoms-auth.service.ts
Normal file
108
client/src/app/services/podnoms-auth.service.ts
Normal 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 {}
|
||||
}
|
||||
9
client/src/assets/util.js
Normal file
9
client/src/assets/util.js
Normal 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, ' '));
|
||||
}
|
||||
31
client/src/facebook-auth.html
Normal file
31
client/src/facebook-auth.html
Normal 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>
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user