diff --git a/app/Classes/Page.php b/app/Classes/Page.php index ce3b79e..20f09ec 100644 --- a/app/Classes/Page.php +++ b/app/Classes/Page.php @@ -36,6 +36,7 @@ class Page $this->logo = new ANSI; $this->left_box = new Font; $this->crlf = $crlf; + $this->text = ''; } public function __get($key) @@ -114,7 +115,8 @@ class Page */ public function addText(string $text,bool $right=FALSE) { - $this->text = $text; + $this->text .= $text; + $this->text_right = $right; } diff --git a/app/Http/Controllers/SystemController.php b/app/Http/Controllers/SystemController.php index ff51733..548ac8c 100644 --- a/app/Http/Controllers/SystemController.php +++ b/app/Http/Controllers/SystemController.php @@ -7,11 +7,13 @@ use Illuminate\Support\Collection; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Notification; use Illuminate\Support\ViewErrorBag; use App\Http\Requests\SystemRegister; -use App\Models\{Address,Echoarea,System,SystemZone,Zone}; +use App\Models\{Address,Echoarea,Setup,System,SystemZone,Zone}; +use App\Notifications\AddressLink; use App\Rules\{FidoInteger,TwoByteInteger}; class SystemController extends Controller @@ -258,6 +260,8 @@ class SystemController extends Controller */ public function add_edit(SystemRegister $request,System $o) { + $this->authorize('update',$o); + if ($request->post()) { foreach (['name','location','sysop','hold','phone','address','port','active','method','notes','mailer_type','mailer_address','mailer_port','zt_id'] as $key) $o->{$key} = $request->post($key); @@ -269,11 +273,9 @@ class SystemController extends Controller $o->load(['addresses.zone.domain']); - return Gate::check('update',$o) - ? view('system.addedit') + return view('system.addedit') ->with('action',$o->exists ? 'update' : 'create') - ->with('o',$o) - : redirect()->to('user/system/register'); + ->with('o',$o); } public function api_address(Request $request,System $o): Collection @@ -300,6 +302,42 @@ class SystemController extends Controller ->get(); } + /** + * Identify all the addresses from systems that are not owned by a user + * + * @param Request $request + * @return Collection + */ + public function api_orphan_address(Request $request): Collection + { + $result = collect(); + + list($zone_id,$host_id,$node_id,$point_id,$domain) = sscanf($request->query('term'),'%d:%d/%d.%d@%s'); + + # Look for Systems + foreach (Address::select(['addresses.id','systems.name',DB::raw('systems.id AS system_id'),'zones.zone_id','region_id','host_id','node_id','point_id','addresses.zone_id']) + ->join('zones',['zones.id'=>'addresses.zone_id']) + ->rightjoin('systems',['systems.id'=>'addresses.system_id']) + ->when($zone_id || $host_id || $node_id,function($query) use ($zone_id,$host_id,$node_id) { + return $query + ->when($zone_id,function($q,$zone_id) { return $q->where('zones.zone_id',$zone_id); }) + ->where(function($q) use ($host_id) { + return $q + ->when($host_id,function($q,$host_id) { return $q->where('region_id',$host_id); }) + ->when($host_id,function($q,$host_id) { return $q->orWhere('host_id',$host_id); }); + }) + ->when($node_id,function($q,$node_id) { return $q->where('node_id',$node_id); }); + }) + ->orWhere('systems.name','ilike','%'.$request->query('term').'%') + ->orderBy('systems.name') + ->get() as $o) + { + $result->push(['id'=>$o->id,'name'=>sprintf('%s (%s)',$o->ftn3d,$o->name),'category'=>'Systems']); + } + + return $result; + } + /** * Delete address assigned to a host * @@ -450,8 +488,26 @@ class SystemController extends Controller */ public function system_register(SystemRegister $request) { + // Step 1, show the user a form to select an existing defined system + if ($request->isMethod('GET')) + return view('user.system.register'); + $o = System::findOrNew($request->system_id); + // If the system exists, and we are 'register', we'll start the address claim process + if ($o->exists && $request->action == 'register') { + $validate = Setup::findOrFail(config('app.id'))->system->inMyZones($o->addresses); + + // If we have addresses, we'll trigger the routed netmail + if ($validate->count()) + Notification::route('netmail',$x=$validate->first())->notify(new AddressLink($x,Auth::user())); + + return view('user.system.widget.register_confirm') + ->with('validate',$validate) + ->with('o',$o); + } + + // If the system doesnt exist, we'll create it if (! $o->exist) { $o->sysop = Auth::user()->name; @@ -479,4 +535,4 @@ class SystemController extends Controller ->with('o',$o) ->with('errors',new ViewErrorBag); } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index f8cde4b..3ee0363 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -5,8 +5,9 @@ namespace App\Http\Controllers; use Illuminate\Auth\Events\Registered; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Validator; -use App\Models\User; +use App\Models\{Address,User}; class UserController extends Controller { @@ -63,6 +64,36 @@ class UserController extends Controller return view('user.home'); } + public function link(Request $request) + { + if ($request->post()) { + $request->validate([ + 'address_id'=>'required|exists:addresses,id', + 'code'=>'required:string', + ]); + + $ao = Address::findOrFail($request->address_id); + if ($ao->check_activation(Auth::user(),$request->code)) { + $ao->validated = TRUE; + $ao->save(); + + $ao->system->users()->save(Auth::user()); + + return redirect()->to('/'); + + } else { + $validator = Validator::make([],[]); + $validator->errors()->add( + 'code', 'Invalid Code!' + ); + + return back()->withErrors($validator); + } + } + + return view('user.link'); + } + public function register() { return view('user/system/register'); diff --git a/app/Http/Requests/SystemRegister.php b/app/Http/Requests/SystemRegister.php index 4ad988c..37f95e9 100644 --- a/app/Http/Requests/SystemRegister.php +++ b/app/Http/Requests/SystemRegister.php @@ -21,7 +21,7 @@ class SystemRegister extends FormRequest { $this->so = System::findOrNew($request->system_id); - return Gate::allows($this->so->exists ? 'update' : 'create',$this->so); + return Gate::allows($this->so->users->count() ? 'update' : 'register',$this->so); } /** @@ -31,7 +31,7 @@ class SystemRegister extends FormRequest */ public function rules(Request $request) { - if (! $request->isMethod('post')) + if ((! $request->isMethod('post')) || ($request->action == 'register')) return []; if ((! $this->so->exists) && ($request->action == 'create')) { diff --git a/app/Models/Address.php b/app/Models/Address.php index f751f72..1a356e0 100644 --- a/app/Models/Address.php +++ b/app/Models/Address.php @@ -7,6 +7,7 @@ use Exception; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; @@ -369,6 +370,41 @@ class Address extends Model return ($o && $o->system->active) ? $o : NULL; } + /** + * Create an activation code for this address + * + * @param User $uo + * @return string + */ + public function set_activation(User $uo): string + { + return sprintf('%x:%s', + $this->id, + substr(md5(sprintf('%d:%x',$uo->id,timew($this->updated_at))),0,10) + ); + } + + /** + * Check the user's activation code for this address is correct + * + * @param User $uo + * @param string $code + * @return bool + */ + public function check_activation(User $uo,string $code): bool + { + try { + Log::info(sprintf('%s:Checking Activation code [%s] invalid for user [%d]',self::LOGKEY,$code,$uo->id)); + + return ($code == $this->set_activation($uo)); + + } catch (\Exception $e) { + Log::error(sprintf('%s:! Activation code [%s] invalid for user [%d]',self::LOGKEY,$code,$uo->id)); + + return FALSE; + } + } + /** * Netmail waiting to be sent to this system * diff --git a/app/Models/Netmail.php b/app/Models/Netmail.php index 9f62233..a205c27 100644 --- a/app/Models/Netmail.php +++ b/app/Models/Netmail.php @@ -9,13 +9,13 @@ use Illuminate\Support\Facades\Log; use App\Classes\FTN\Message; use App\Interfaces\Packet; -use App\Traits\EncodeUTF8; +use App\Traits\{EncodeUTF8,MsgID}; final class Netmail extends Model implements Packet { private const LOGKEY = 'MN-'; - use SoftDeletes,EncodeUTF8; + use SoftDeletes,EncodeUTF8,MsgID; private const cast_utf8 = [ 'to', diff --git a/app/Models/Setup.php b/app/Models/Setup.php index 82eed32..2e80a70 100644 --- a/app/Models/Setup.php +++ b/app/Models/Setup.php @@ -8,7 +8,9 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\File; /** - * Class Setup + * This class represents our configuration. + * + * Our 'System' is defined by system_id, and from it we can find out our BBS name and addresses. * * @package App\Models * @property Collection nodes @@ -47,37 +49,6 @@ class Setup extends Model // Our non model attributes and values private array $internal = []; - public static function product_id(int $c=self::PRODUCT_ID): string - { - return hexstr($c); - } - - /* RELATIONS */ - - public function system() - { - return $this->belongsTo(System::class); - } - - /* ATTRIBUTES */ - - public function getLocationAttribute() - { - return $this->system->location; - } - - public function getSysopAttribute() - { - return $this->system->sysop; - } - - public function getSystemNameAttribute() - { - return $this->system->name; - } - - /* METHODS */ - public function __construct(array $attributes = []) { parent::__construct($attributes); @@ -143,6 +114,49 @@ class Setup extends Model } } + /** + * The Mailer Product ID in hex. + * + * @param int $c + * @return string + * @throws Exception + */ + public static function product_id(int $c=self::PRODUCT_ID): string + { + return hexstr($c); + } + + /* RELATIONS */ + + /** + * The defined system that this setup is valid for + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function system() + { + return $this->belongsTo(System::class); + } + + /* ATTRIBUTES */ + + public function getLocationAttribute() + { + return $this->system->location; + } + + public function getSysopAttribute() + { + return $this->system->sysop; + } + + public function getSystemNameAttribute() + { + return $this->system->name; + } + + /* METHODS */ + /* BINKP OPTIONS: BINKP_OPT_* */ public function binkpOptionClear(int $key): void diff --git a/app/Models/System.php b/app/Models/System.php index ce07031..afc3a8c 100644 --- a/app/Models/System.php +++ b/app/Models/System.php @@ -133,4 +133,20 @@ class System extends Model return $item->role & $type; }); } + + /** + * Parse the addresses and return which ones are in my zones + * + * @param \Illuminate\Database\Eloquent\Collection $addresses + * @param int $type + * @return Collection + */ + public function inMyZones(Collection $addresses,int $type=(Address::NODE_HC|Address::NODE_ACTIVE|Address::NODE_PVT|Address::NODE_POINT)): Collection + { + $myzones = $this->addresses->pluck('zone_id')->unique(); + + return $addresses->filter(function($item) use ($myzones,$type) { + return ($item->role & $type) && ($myzones->search($item->zone_id) !== FALSE); + }); + } } \ No newline at end of file diff --git a/app/Notifications/AddressLink.php b/app/Notifications/AddressLink.php new file mode 100644 index 0000000..efebd1a --- /dev/null +++ b/app/Notifications/AddressLink.php @@ -0,0 +1,104 @@ +queue = 'netmail'; + $this->ao = $ao; + $this->uo = $uo; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['netmail']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return Netmail + * @throws \Exception + */ + public function toNetmail($notifiable): Netmail + { + Log::info(sprintf('%s:Sending a link code for address [%s]',self::LOGKEY,$this->ao->ftn)); + + $so = Setup::findOrFail(config('app.id'))->system; + + $o = new Netmail; + $o->to = $this->ao->system->sysop; + $o->from = Setup::PRODUCT_NAME; + $o->subject = 'Address Link Code'; + $o->datetime = Carbon::now(); + $o->tzoffset = $o->datetime->utcOffset(); + + $o->fftn_id = $so->match($this->ao->zone)->first()->id; + $o->tftn_id = $this->ao->id; + $o->flags = Message::FLAG_LOCAL; + $o->cost = 0; + + $o->tagline = 'Address Linking...'; + $o->tearline = sprintf('%s (%04X)',Setup::PRODUCT_NAME,Setup::PRODUCT_ID); + + // Message + $msg = new Page; + $msg->addLogo(new ANSI(base_path('public/logo/netmail.bin'))); + + $header = new Thick; + $header->addText(ANSI::ansi_code([1,37]).'Clearing Houz'); + $msg->addHeader($header,'FTN Mailer and Tosser',TRUE,0xc4); + + $lbc = new Thin; + $lbc->addText('#link'); + $msg->addLeftBoxContent($lbc); + + $msg->addText(sprintf( + "Hi %s,\r\r". + "This message is to link your address [%s] to your user ID in the Clearing Houz web site.\r\r". + "If you didnt start this process, then you can safely ignore this netmail. But if you wanted to link this address, please head over to [%s] and paste in the following:\r\r%s\r", + $this->ao->system->sysop, + $this->ao->ftn3d, + url('/link'), + $this->ao->set_activation($this->uo) + )); + + $o->msg = $msg->render(); + $o->save(); + + return $o; + } +} \ No newline at end of file diff --git a/app/Notifications/Channels/NetmailChannel.php b/app/Notifications/Channels/NetmailChannel.php index 2f3c851..58afb8b 100644 --- a/app/Notifications/Channels/NetmailChannel.php +++ b/app/Notifications/Channels/NetmailChannel.php @@ -34,7 +34,7 @@ class NetmailChannel * * @param mixed $notifiable * @param \Illuminate\Notifications\Notification $notification - * @return \Psr\Http\Message\ResponseInterface|null + * @return \Psr\Http\Message\ResponseInterface|void */ public function send($notifiable,Notification $notification) { @@ -42,9 +42,8 @@ class NetmailChannel return; $o = $notification->toNetmail($notifiable); - Log::info(sprintf('%s:Test Netmail created [%s]',self::LOGKEY,$o->id)); Job::dispatch($ao); - Log::info(sprintf('%s:Dispatched job to pool address [%s]',self::LOGKEY,$ao->ftn)); + Log::info(sprintf('%s:Sent netmail [%s] via [%s]',self::LOGKEY,$o->msgid,$ao->ftn)); } } \ No newline at end of file diff --git a/app/Notifications/NetmailTest.php b/app/Notifications/NetmailTest.php index bb24fa4..e255c3c 100644 --- a/app/Notifications/NetmailTest.php +++ b/app/Notifications/NetmailTest.php @@ -64,7 +64,7 @@ class NetmailTest extends Notification //implements ShouldQueue $o->datetime = Carbon::now(); $o->tzoffset = $o->datetime->utcOffset(); - $o->fftn_id = $so->match($this->ao->zone)->first(); + $o->fftn_id = $so->match($this->ao->zone)->first()->id; $o->tftn_id = $this->ao->id; $o->flags = Message::FLAG_LOCAL; $o->cost = 0; diff --git a/app/Policies/SystemPolicy.php b/app/Policies/SystemPolicy.php index 6721094..15801db 100644 --- a/app/Policies/SystemPolicy.php +++ b/app/Policies/SystemPolicy.php @@ -26,6 +26,18 @@ class SystemPolicy return ($user->isAdmin() || (! $system->exists)); } + /** + * Can the user register this system + * + * @param User $user + * @param System $system + * @return bool + */ + public function register(User $user,System $system): bool + { + return ! $system->users->count() || $system->users->has($user); + } + /** * Determine whether the user can update the model. * diff --git a/database/migrations/2021_11_26_114344_add_validated.php b/database/migrations/2021_11_26_114344_add_validated.php new file mode 100644 index 0000000..fc956b8 --- /dev/null +++ b/database/migrations/2021_11_26_114344_add_validated.php @@ -0,0 +1,32 @@ +boolean('validated')->default(FALSE); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('addresses', function (Blueprint $table) { + $table->dropColumn('validated'); + }); + } +} diff --git a/resources/views/system/home.blade.php b/resources/views/system/home.blade.php index 5883e69..e0aaa4b 100644 --- a/resources/views/system/home.blade.php +++ b/resources/views/system/home.blade.php @@ -13,7 +13,10 @@
This system is aware of the following systems @can('create',(new \App\Models\System))(you can add more)@endcan:
+This system is aware of the following systems + @can('create',(new \App\Models\System))(you can register more):@endcan + @can('admin',(new \App\Models\System))(you can add more):@endcan +
@if (\App\Models\System::active()->count() == 0) @can('create',(new \App\Models\System)) @@ -39,7 +42,7 @@ @foreach (\App\Models\System::active()->with(['addresses.zone.domain'])->get() as $oo)