mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-14 01:02:57 -07:00
parent
51fbd56c44
commit
ace90da7ed
15 changed files with 383 additions and 64 deletions
|
@ -90,6 +90,7 @@ namespace Ombi.DependencyInjection
|
||||||
{
|
{
|
||||||
services.AddTransient<IRequestServiceMain, RequestService>();
|
services.AddTransient<IRequestServiceMain, RequestService>();
|
||||||
services.AddSingleton<INotificationService, NotificationService>();
|
services.AddSingleton<INotificationService, NotificationService>();
|
||||||
|
services.AddSingleton<IEmailProvider, GenericEmailProvider>();
|
||||||
services.AddTransient<INotificationHelper, NotificationHelper>();
|
services.AddTransient<INotificationHelper, NotificationHelper>();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,11 @@ namespace Ombi.Notifications.Agents
|
||||||
{
|
{
|
||||||
public class EmailNotification : BaseNotification<EmailNotificationSettings>, IEmailNotification
|
public class EmailNotification : BaseNotification<EmailNotificationSettings>, IEmailNotification
|
||||||
{
|
{
|
||||||
public EmailNotification(ISettingsService<EmailNotificationSettings> settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t) : base(settings, r, m, t)
|
public EmailNotification(ISettingsService<EmailNotificationSettings> settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov) : base(settings, r, m, t)
|
||||||
{
|
{
|
||||||
|
EmailProvider = prov;
|
||||||
}
|
}
|
||||||
|
private IEmailProvider EmailProvider { get; }
|
||||||
public override string NotificationName => nameof(EmailNotification);
|
public override string NotificationName => nameof(EmailNotification);
|
||||||
|
|
||||||
protected override bool ValidateConfiguration(EmailNotificationSettings settings)
|
protected override bool ValidateConfiguration(EmailNotificationSettings settings)
|
||||||
|
@ -166,44 +167,7 @@ namespace Ombi.Notifications.Agents
|
||||||
|
|
||||||
protected override async Task Send(NotificationMessage model, EmailNotificationSettings settings)
|
protected override async Task Send(NotificationMessage model, EmailNotificationSettings settings)
|
||||||
{
|
{
|
||||||
try
|
await EmailProvider.Send(model, settings);
|
||||||
{
|
|
||||||
var body = new BodyBuilder
|
|
||||||
{
|
|
||||||
HtmlBody = model.Message,
|
|
||||||
//TextBody = model.Other["PlainTextBody"]
|
|
||||||
};
|
|
||||||
|
|
||||||
var message = new MimeMessage
|
|
||||||
{
|
|
||||||
Body = body.ToMessageBody(),
|
|
||||||
Subject = model.Subject
|
|
||||||
};
|
|
||||||
message.From.Add(new MailboxAddress(settings.Sender, settings.Sender));
|
|
||||||
message.To.Add(new MailboxAddress(model.To, model.To));
|
|
||||||
|
|
||||||
using (var client = new SmtpClient())
|
|
||||||
{
|
|
||||||
client.Connect(settings.Host, settings.Port); // Let MailKit figure out the correct SecureSocketOptions.
|
|
||||||
|
|
||||||
// Note: since we don't have an OAuth2 token, disable
|
|
||||||
// the XOAUTH2 authentication mechanism.
|
|
||||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
|
||||||
|
|
||||||
if (settings.Authentication)
|
|
||||||
{
|
|
||||||
client.Authenticate(settings.Username, settings.Password);
|
|
||||||
}
|
|
||||||
//Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated);
|
|
||||||
await client.SendAsync(message);
|
|
||||||
await client.DisconnectAsync(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
//Log.Error(e);
|
|
||||||
throw new InvalidOperationException(e.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task Test(NotificationOptions model, EmailNotificationSettings settings)
|
protected override async Task Test(NotificationOptions model, EmailNotificationSettings settings)
|
||||||
|
|
54
src/Ombi.Notifications/GenericEmailProvider.cs
Normal file
54
src/Ombi.Notifications/GenericEmailProvider.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MailKit.Net.Smtp;
|
||||||
|
using MimeKit;
|
||||||
|
using Ombi.Notifications.Models;
|
||||||
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
|
||||||
|
namespace Ombi.Notifications
|
||||||
|
{
|
||||||
|
public class GenericEmailProvider : IEmailProvider
|
||||||
|
{
|
||||||
|
public async Task Send(NotificationMessage model, EmailNotificationSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var body = new BodyBuilder
|
||||||
|
{
|
||||||
|
HtmlBody = model.Message,
|
||||||
|
//TextBody = model.Other["PlainTextBody"]
|
||||||
|
};
|
||||||
|
|
||||||
|
var message = new MimeMessage
|
||||||
|
{
|
||||||
|
Body = body.ToMessageBody(),
|
||||||
|
Subject = model.Subject
|
||||||
|
};
|
||||||
|
message.From.Add(new MailboxAddress(settings.Sender, settings.Sender));
|
||||||
|
message.To.Add(new MailboxAddress(model.To, model.To));
|
||||||
|
|
||||||
|
using (var client = new SmtpClient())
|
||||||
|
{
|
||||||
|
client.Connect(settings.Host, settings.Port); // Let MailKit figure out the correct SecureSocketOptions.
|
||||||
|
|
||||||
|
// Note: since we don't have an OAuth2 token, disable
|
||||||
|
// the XOAUTH2 authentication mechanism.
|
||||||
|
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||||
|
|
||||||
|
if (settings.Authentication)
|
||||||
|
{
|
||||||
|
client.Authenticate(settings.Username, settings.Password);
|
||||||
|
}
|
||||||
|
//Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated);
|
||||||
|
await client.SendAsync(message);
|
||||||
|
await client.DisconnectAsync(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//Log.Error(e);
|
||||||
|
throw new InvalidOperationException(e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/Ombi.Notifications/IEmailProvider.cs
Normal file
11
src/Ombi.Notifications/IEmailProvider.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Notifications.Models;
|
||||||
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
|
||||||
|
namespace Ombi.Notifications
|
||||||
|
{
|
||||||
|
public interface IEmailProvider
|
||||||
|
{
|
||||||
|
Task Send(NotificationMessage model, EmailNotificationSettings settings);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import { RouterModule, Routes } from '@angular/router';
|
||||||
import { HttpModule } from '@angular/http';
|
import { HttpModule } from '@angular/http';
|
||||||
|
|
||||||
// Third Party
|
// Third Party
|
||||||
import { ButtonModule, DialogModule } from 'primeng/primeng';
|
import { ButtonModule, DialogModule, CaptchaModule } from 'primeng/primeng';
|
||||||
import { GrowlModule } from 'primeng/components/growl/growl';
|
import { GrowlModule } from 'primeng/components/growl/growl';
|
||||||
import { DataTableModule, SharedModule } from 'primeng/primeng';
|
import { DataTableModule, SharedModule } from 'primeng/primeng';
|
||||||
//import { DragulaModule, DragulaService } from 'ng2-dragula/ng2-dragula';
|
//import { DragulaModule, DragulaService } from 'ng2-dragula/ng2-dragula';
|
||||||
|
@ -17,6 +17,8 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
import { LoginComponent } from './login/login.component';
|
import { LoginComponent } from './login/login.component';
|
||||||
|
import { ResetPasswordComponent } from './login/resetpassword.component';
|
||||||
|
import { TokenResetPasswordComponent } from './login/tokenresetpassword.component';
|
||||||
import { LandingPageComponent } from './landingpage/landingpage.component';
|
import { LandingPageComponent } from './landingpage/landingpage.component';
|
||||||
import { PageNotFoundComponent } from './errors/not-found.component';
|
import { PageNotFoundComponent } from './errors/not-found.component';
|
||||||
|
|
||||||
|
@ -44,6 +46,7 @@ const routes: Routes = [
|
||||||
|
|
||||||
//{ path: 'requests-grid', component: RequestGridComponent },
|
//{ path: 'requests-grid', component: RequestGridComponent },
|
||||||
{ path: 'login', component: LoginComponent },
|
{ path: 'login', component: LoginComponent },
|
||||||
|
{ path: 'reset', component: ResetPasswordComponent },
|
||||||
{ path: 'landingpage', component: LandingPageComponent }
|
{ path: 'landingpage', component: LandingPageComponent }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -71,13 +74,16 @@ const routes: Routes = [
|
||||||
MdTabsModule,
|
MdTabsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
UserManagementModule,
|
UserManagementModule,
|
||||||
RequestsModule
|
RequestsModule,
|
||||||
|
CaptchaModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
PageNotFoundComponent,
|
PageNotFoundComponent,
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
LandingPageComponent,
|
LandingPageComponent,
|
||||||
|
ResetPasswordComponent,
|
||||||
|
TokenResetPasswordComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
RequestService,
|
RequestService,
|
||||||
|
|
|
@ -30,3 +30,8 @@ export interface IUpdateLocalUser extends IUser {
|
||||||
confirmNewPassword: string
|
confirmNewPassword: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IResetPasswordToken{
|
||||||
|
email:string,
|
||||||
|
token:string,
|
||||||
|
password:string
|
||||||
|
}
|
|
@ -17,8 +17,8 @@ include the remember me checkbox
|
||||||
<input type="password" id="inputPassword" class="form-control" formControlName="password" placeholder="Password">
|
<input type="password" id="inputPassword" class="form-control" formControlName="password" placeholder="Password">
|
||||||
<button class="btn btn-success-outline" [disabled]="form.invalid" type="submit">Sign in</button>
|
<button class="btn btn-success-outline" [disabled]="form.invalid" type="submit">Sign in</button>
|
||||||
</form><!-- /form -->
|
</form><!-- /form -->
|
||||||
<a href="#" class="forgot-password">
|
<a [routerLink]="['/reset']" class="forgot-password">
|
||||||
Forgot the password?
|
Reset your password?
|
||||||
</a>
|
</a>
|
||||||
</div><!-- /card-container -->
|
</div><!-- /card-container -->
|
||||||
</div><!-- /container -->
|
</div><!-- /container -->
|
||||||
|
|
20
src/Ombi/ClientApp/app/login/resetpassword.component.html
Normal file
20
src/Ombi/ClientApp/app/login/resetpassword.component.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<!--
|
||||||
|
you can substitue the span of reauth email for a input with the email and
|
||||||
|
include the remember me checkbox
|
||||||
|
-->
|
||||||
|
<div *ngIf="form && customizationSettings">
|
||||||
|
<div class="container" id="login">
|
||||||
|
<div class="card card-container">
|
||||||
|
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
|
||||||
|
<div *ngIf="!customizationSettings.logo"><img id="profile-img" class="profile-img-card" src="/images/ms-icon-150x150.png" /></div>
|
||||||
|
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="profile-img-card" [src]="customizationSettings.logo" /></div>
|
||||||
|
<p id="profile-name" class="profile-name-card"></p>
|
||||||
|
|
||||||
|
<form class="form-signin" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||||
|
<input type="email" id="inputEmail" class="form-control" formControlName="email" placeholder="Email Address" autofocus>
|
||||||
|
<button class="btn btn-success-outline" [disabled]="form.invalid" type="submit">Reset Password</button>
|
||||||
|
|
||||||
|
</form><!-- /form -->
|
||||||
|
</div><!-- /card-container -->
|
||||||
|
</div><!-- /container -->
|
||||||
|
</div>
|
42
src/Ombi/ClientApp/app/login/resetpassword.component.ts
Normal file
42
src/Ombi/ClientApp/app/login/resetpassword.component.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
|
import { IdentityService } from '../services/identity.service';
|
||||||
|
import { NotificationService } from '../services/notification.service';
|
||||||
|
import { SettingsService } from '../services/settings.service';
|
||||||
|
import { ICustomizationSettings } from '../interfaces/ISettings';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './resetpassword.component.html',
|
||||||
|
styleUrls: ['./login.component.scss']
|
||||||
|
})
|
||||||
|
export class ResetPasswordComponent implements OnInit {
|
||||||
|
|
||||||
|
|
||||||
|
constructor(private identityService: IdentityService, private notify: NotificationService,
|
||||||
|
private fb: FormBuilder, private settingsService: SettingsService) {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
email: ["", [Validators.required]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
|
customizationSettings: ICustomizationSettings;
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onSubmit(form: FormGroup): void {
|
||||||
|
if (form.invalid) {
|
||||||
|
this.notify.error("Validation", "Email address is required");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.identityService.submitResetPassword(form.value.email).subscribe(x => {
|
||||||
|
x.errors.forEach((val) => {
|
||||||
|
this.notify.success("Password Reset", val);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
<!--
|
||||||
|
you can substitue the span of reauth email for a input with the email and
|
||||||
|
include the remember me checkbox
|
||||||
|
-->
|
||||||
|
<div *ngIf="form && customizationSettings">
|
||||||
|
<div class="container" id="login">
|
||||||
|
<div class="card card-container">
|
||||||
|
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
|
||||||
|
<div *ngIf="!customizationSettings.logo"><img id="profile-img" class="profile-img-card" src="/images/ms-icon-150x150.png" /></div>
|
||||||
|
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="profile-img-card" [src]="customizationSettings.logo" /></div>
|
||||||
|
<p id="profile-name" class="profile-name-card"></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div *ngIf="form.value.password !== form.value.confirmPassword" class="alert alert-danger">The passwords do not match</div>
|
||||||
|
<div *ngIf="form.invalid && form.dirty" class="alert alert-danger">
|
||||||
|
|
||||||
|
<div *ngIf="form.get('password').hasError('required')">The Password is required</div>
|
||||||
|
<div *ngIf="form.get('email').hasError('required')">The Email is required</div>
|
||||||
|
<div *ngIf="form.get('confirmPassword').hasError('required')">The Confirm Password is required</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="form-signin" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||||
|
<input type="email" id="inputEmail" class="form-control" formControlName="email" placeholder="Email Address" autofocus>
|
||||||
|
<input type="password" class="form-control" formControlName="password">
|
||||||
|
<input type="password" class="form-control" formControlName="confirmPassword">
|
||||||
|
<button class="btn btn-success-outline" [disabled]="form.invalid || !captcha" type="submit">Reset Password</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
<!-- /form -->
|
||||||
|
<a href="#" class="forgot-password">
|
||||||
|
Forgot the password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- /card-container -->
|
||||||
|
</div>
|
||||||
|
<!-- /container -->
|
||||||
|
</div>
|
57
src/Ombi/ClientApp/app/login/tokenresetpassword.component.ts
Normal file
57
src/Ombi/ClientApp/app/login/tokenresetpassword.component.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
|
import { IdentityService } from '../services/identity.service';
|
||||||
|
import { NotificationService } from '../services/notification.service';
|
||||||
|
import { SettingsService } from '../services/settings.service';
|
||||||
|
import { ICustomizationSettings } from '../interfaces/ISettings';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './tokenresetpassword.component.html',
|
||||||
|
styleUrls: ['./login.component.scss']
|
||||||
|
})
|
||||||
|
export class TokenResetPasswordComponent implements OnInit {
|
||||||
|
constructor(private identityService: IdentityService, private router: Router, private route: ActivatedRoute, private notify: NotificationService,
|
||||||
|
private fb: FormBuilder, private settingsService: SettingsService) {
|
||||||
|
|
||||||
|
this.route.params
|
||||||
|
.subscribe(params => {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
email: ["", [Validators.required]],
|
||||||
|
password: ["", [Validators.required]],
|
||||||
|
confirmPassword: ["", [Validators.required]],
|
||||||
|
token: [params['token']]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
|
customizationSettings: ICustomizationSettings;
|
||||||
|
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onSubmit(form: FormGroup): void {
|
||||||
|
if (form.invalid) {
|
||||||
|
this.notify.error("Validation", "Email address is required");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.identityService.resetPassword(form.value).subscribe(x => {
|
||||||
|
if (x.successful) {
|
||||||
|
this.notify.success("Success", `Your Password has been reset`)
|
||||||
|
this.router.navigate(['login']);
|
||||||
|
} else {
|
||||||
|
x.errors.forEach((val) => {
|
||||||
|
this.notify.error("Error", val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import { Http } from '@angular/http';
|
||||||
import { Observable } from 'rxjs/Rx';
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
|
||||||
import { ServiceAuthHelpers } from './service.helpers';
|
import { ServiceAuthHelpers } from './service.helpers';
|
||||||
import { IUser, IUpdateLocalUser, ICheckbox, IIdentityResult } from '../interfaces/IUser';
|
import { IUser, IUpdateLocalUser, ICheckbox, IIdentityResult, IResetPasswordToken } from '../interfaces/IUser';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -47,6 +47,14 @@ export class IdentityService extends ServiceAuthHelpers {
|
||||||
return this.http.delete(`${this.url}/${user.id}`, { headers: this.headers }).map(this.extractData);
|
return this.http.delete(`${this.url}/${user.id}`, { headers: this.headers }).map(this.extractData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
submitResetPassword(email:string): Observable<IIdentityResult>{
|
||||||
|
return this.regularHttp.post(this.url + 'reset', JSON.stringify({email:email}), { headers: this.headers }).map(this.extractData);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetPassword(token: IResetPasswordToken):Observable<IIdentityResult>{
|
||||||
|
return this.regularHttp.post(this.url + 'resetpassword', JSON.stringify(token), { headers: this.headers }).map(this.extractData);
|
||||||
|
}
|
||||||
|
|
||||||
hasRole(role: string): boolean {
|
hasRole(role: string): boolean {
|
||||||
var roles = localStorage.getItem("roles") as string[] | null;
|
var roles = localStorage.getItem("roles") as string[] | null;
|
||||||
if (roles) {
|
if (roles) {
|
||||||
|
|
|
@ -9,15 +9,22 @@ using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
using Ombi.Attributes;
|
using Ombi.Attributes;
|
||||||
|
using Ombi.Config;
|
||||||
using Ombi.Core.Claims;
|
using Ombi.Core.Claims;
|
||||||
using Ombi.Core.Helpers;
|
using Ombi.Core.Helpers;
|
||||||
using Ombi.Core.Models.UI;
|
using Ombi.Core.Models.UI;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Models;
|
using Ombi.Models;
|
||||||
using Ombi.Models.Identity;
|
using Ombi.Models.Identity;
|
||||||
|
using Ombi.Notifications;
|
||||||
|
using Ombi.Notifications.Models;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using IdentityResult = Ombi.Models.Identity.IdentityResult;
|
using OmbiIdentityResult = Ombi.Models.Identity.IdentityResult;
|
||||||
|
|
||||||
namespace Ombi.Controllers
|
namespace Ombi.Controllers
|
||||||
{
|
{
|
||||||
|
@ -28,16 +35,27 @@ namespace Ombi.Controllers
|
||||||
[PowerUser]
|
[PowerUser]
|
||||||
public class IdentityController : BaseV1ApiController
|
public class IdentityController : BaseV1ApiController
|
||||||
{
|
{
|
||||||
public IdentityController(UserManager<OmbiUser> user, IMapper mapper, RoleManager<IdentityRole> rm)
|
public IdentityController(UserManager<OmbiUser> user, IMapper mapper, RoleManager<IdentityRole> rm, IEmailProvider prov,
|
||||||
|
ISettingsService<EmailNotificationSettings> s,
|
||||||
|
ISettingsService<CustomizationSettings> c,
|
||||||
|
IOptions<UserSettings> userSettings)
|
||||||
{
|
{
|
||||||
UserManager = user;
|
UserManager = user;
|
||||||
Mapper = mapper;
|
Mapper = mapper;
|
||||||
RoleManager = rm;
|
RoleManager = rm;
|
||||||
|
EmailProvider = prov;
|
||||||
|
EmailSettings = s;
|
||||||
|
CustomizationSettings = c;
|
||||||
|
UserSettings = userSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserManager<OmbiUser> UserManager { get; }
|
private UserManager<OmbiUser> UserManager { get; }
|
||||||
private RoleManager<IdentityRole> RoleManager { get; }
|
private RoleManager<IdentityRole> RoleManager { get; }
|
||||||
private IMapper Mapper { get; }
|
private IMapper Mapper { get; }
|
||||||
|
private IEmailProvider EmailProvider { get; }
|
||||||
|
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
|
||||||
|
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
|
||||||
|
private IOptions<UserSettings> UserSettings { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is what the Wizard will call when creating the user for the very first time.
|
/// This is what the Wizard will call when creating the user for the very first time.
|
||||||
|
@ -167,7 +185,7 @@ namespace Ombi.Controllers
|
||||||
/// <param name = "user" > The user.</param>
|
/// <param name = "user" > The user.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IdentityResult> CreateUser([FromBody] UserViewModel user)
|
public async Task<OmbiIdentityResult> CreateUser([FromBody] UserViewModel user)
|
||||||
{
|
{
|
||||||
if (!EmailValidator.IsValidEmail(user.EmailAddress))
|
if (!EmailValidator.IsValidEmail(user.EmailAddress))
|
||||||
{
|
{
|
||||||
|
@ -185,7 +203,7 @@ namespace Ombi.Controllers
|
||||||
if (!userResult.Succeeded)
|
if (!userResult.Succeeded)
|
||||||
{
|
{
|
||||||
// We did not create the user
|
// We did not create the user
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Errors = userResult.Errors.Select(x => x.Description).ToList()
|
Errors = userResult.Errors.Select(x => x.Description).ToList()
|
||||||
};
|
};
|
||||||
|
@ -201,13 +219,13 @@ namespace Ombi.Controllers
|
||||||
messages.AddRange(errors.Errors.Select(x => x.Description).ToList());
|
messages.AddRange(errors.Errors.Select(x => x.Description).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Errors = messages
|
Errors = messages
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Successful = true
|
Successful = true
|
||||||
};
|
};
|
||||||
|
@ -220,7 +238,7 @@ namespace Ombi.Controllers
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPut("local")]
|
[HttpPut("local")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IdentityResult> UpdateLocalUser([FromBody] UpdateLocalUserModel ui)
|
public async Task<OmbiIdentityResult> UpdateLocalUser([FromBody] UpdateLocalUserModel ui)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(ui.CurrentPassword))
|
if (string.IsNullOrEmpty(ui.CurrentPassword))
|
||||||
{
|
{
|
||||||
|
@ -260,7 +278,7 @@ namespace Ombi.Controllers
|
||||||
var updateResult = await UserManager.UpdateAsync(user);
|
var updateResult = await UserManager.UpdateAsync(user);
|
||||||
if (!updateResult.Succeeded)
|
if (!updateResult.Succeeded)
|
||||||
{
|
{
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Errors = updateResult.Errors.Select(x => x.Description).ToList()
|
Errors = updateResult.Errors.Select(x => x.Description).ToList()
|
||||||
};
|
};
|
||||||
|
@ -272,13 +290,13 @@ namespace Ombi.Controllers
|
||||||
|
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Errors = result.Errors.Select(x => x.Description).ToList()
|
Errors = result.Errors.Select(x => x.Description).ToList()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Successful = true
|
Successful = true
|
||||||
};
|
};
|
||||||
|
@ -291,7 +309,7 @@ namespace Ombi.Controllers
|
||||||
/// <param name = "ui" > The user.</param>
|
/// <param name = "ui" > The user.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
public async Task<IdentityResult> UpdateUser([FromBody] UserViewModel ui)
|
public async Task<OmbiIdentityResult> UpdateUser([FromBody] UserViewModel ui)
|
||||||
{
|
{
|
||||||
if (!EmailValidator.IsValidEmail(ui.EmailAddress))
|
if (!EmailValidator.IsValidEmail(ui.EmailAddress))
|
||||||
{
|
{
|
||||||
|
@ -304,7 +322,7 @@ namespace Ombi.Controllers
|
||||||
var updateResult = await UserManager.UpdateAsync(user);
|
var updateResult = await UserManager.UpdateAsync(user);
|
||||||
if (!updateResult.Succeeded)
|
if (!updateResult.Succeeded)
|
||||||
{
|
{
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Errors = updateResult.Errors.Select(x => x.Description).ToList()
|
Errors = updateResult.Errors.Select(x => x.Description).ToList()
|
||||||
};
|
};
|
||||||
|
@ -327,13 +345,13 @@ namespace Ombi.Controllers
|
||||||
messages.AddRange(errors.Errors.Select(x => x.Description).ToList());
|
messages.AddRange(errors.Errors.Select(x => x.Description).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Errors = messages
|
Errors = messages
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Successful = true
|
Successful = true
|
||||||
};
|
};
|
||||||
|
@ -346,7 +364,7 @@ namespace Ombi.Controllers
|
||||||
/// <param name="userId">The user.</param>
|
/// <param name="userId">The user.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpDelete("{userId}")]
|
[HttpDelete("{userId}")]
|
||||||
public async Task<IdentityResult> DeleteUser(string userId)
|
public async Task<OmbiIdentityResult> DeleteUser(string userId)
|
||||||
{
|
{
|
||||||
|
|
||||||
var userToDelete = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
var userToDelete = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
||||||
|
@ -355,13 +373,13 @@ namespace Ombi.Controllers
|
||||||
var result = await UserManager.DeleteAsync(userToDelete);
|
var result = await UserManager.DeleteAsync(userToDelete);
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Successful = true
|
Successful = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Errors = result.Errors.Select(x => x.Description).ToList()
|
Errors = result.Errors.Select(x => x.Description).ToList()
|
||||||
};
|
};
|
||||||
|
@ -391,6 +409,86 @@ namespace Ombi.Controllers
|
||||||
return claims;
|
return claims;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send out the email with the reset link
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="email"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("reset")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
public async Task<OmbiIdentityResult> SubmitResetPassword([FromBody]SubmitPasswordReset email)
|
||||||
|
{
|
||||||
|
// Check if account exists
|
||||||
|
var user = await UserManager.FindByEmailAsync(email.Email);
|
||||||
|
|
||||||
|
var defaultMessage = new OmbiIdentityResult
|
||||||
|
{
|
||||||
|
Successful = true,
|
||||||
|
Errors = new List<string> { "If this account exists you should recieve a password reset link." }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return defaultMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have the user
|
||||||
|
var token = await UserManager.GeneratePasswordResetTokenAsync(user);
|
||||||
|
|
||||||
|
// We now need to email the user with this token
|
||||||
|
var emailSettings = await EmailSettings.GetSettingsAsync();
|
||||||
|
var customizationSettings = await CustomizationSettings.GetSettingsAsync();
|
||||||
|
var appName = (string.IsNullOrEmpty(customizationSettings.ApplicationName)
|
||||||
|
? "Ombi"
|
||||||
|
: customizationSettings.ApplicationName);
|
||||||
|
await EmailProvider.Send(new NotificationMessage
|
||||||
|
{
|
||||||
|
To = user.Email,
|
||||||
|
Subject = $"{appName} Password Reset",
|
||||||
|
Message = $"Hello {user.UserName}, <br/> You recently made a request to reset your {appName} account. Please click the link below to complete the process.<br/><br/>" +
|
||||||
|
$"<a href=\"{UserSettings.Value.WebsiteUrl}/reset/{token}\"> Reset </a>"
|
||||||
|
}, emailSettings);
|
||||||
|
|
||||||
|
return defaultMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the password
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("resetpassword")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
public async Task<OmbiIdentityResult> ResetPassword(ResetPasswordToken token)
|
||||||
|
{
|
||||||
|
var user = await UserManager.FindByEmailAsync(token.Email);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return new OmbiIdentityResult
|
||||||
|
{
|
||||||
|
Successful = false,
|
||||||
|
Errors = new List<string> { "Please check you email." }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenValid = await UserManager.ResetPasswordAsync(user, token.Token, token.Password);
|
||||||
|
|
||||||
|
if (tokenValid.Succeeded)
|
||||||
|
{
|
||||||
|
return new OmbiIdentityResult
|
||||||
|
{
|
||||||
|
Successful = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return new OmbiIdentityResult
|
||||||
|
{
|
||||||
|
Errors = tokenValid.Errors.Select(x => x.Description).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<List<Microsoft.AspNetCore.Identity.IdentityResult>> AddRoles(IEnumerable<ClaimCheckboxes> roles, OmbiUser ombiUser)
|
private async Task<List<Microsoft.AspNetCore.Identity.IdentityResult>> AddRoles(IEnumerable<ClaimCheckboxes> roles, OmbiUser ombiUser)
|
||||||
{
|
{
|
||||||
var roleResult = new List<Microsoft.AspNetCore.Identity.IdentityResult>();
|
var roleResult = new List<Microsoft.AspNetCore.Identity.IdentityResult>();
|
||||||
|
@ -404,9 +502,9 @@ namespace Ombi.Controllers
|
||||||
return roleResult;
|
return roleResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityResult Error(string message)
|
private OmbiIdentityResult Error(string message)
|
||||||
{
|
{
|
||||||
return new IdentityResult
|
return new OmbiIdentityResult
|
||||||
{
|
{
|
||||||
Errors = new List<string> { message }
|
Errors = new List<string> { message }
|
||||||
};
|
};
|
||||||
|
|
9
src/Ombi/Models/Identity/ResetPasswordToken.cs
Normal file
9
src/Ombi/Models/Identity/ResetPasswordToken.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ombi.Models.Identity
|
||||||
|
{
|
||||||
|
public class ResetPasswordToken
|
||||||
|
{
|
||||||
|
public string Token { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
|
}
|
7
src/Ombi/Models/Identity/SubmitPasswordReset.cs
Normal file
7
src/Ombi/Models/Identity/SubmitPasswordReset.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Ombi.Models.Identity
|
||||||
|
{
|
||||||
|
public class SubmitPasswordReset
|
||||||
|
{
|
||||||
|
public string Email { get; set; }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue