diff --git a/.env.example b/.env.example index de03d6f..022a987 100644 --- a/.env.example +++ b/.env.example @@ -57,3 +57,7 @@ AWS_BUCKET= AWS_ENDPOINT= AWS_DEFAULT_REGION=home AWS_USE_PATH_STYLE_ENDPOINT=true + +MATRIX_SERVER= +MATRIX_AS_TOKEN= +MATRIX_HS_TOKEN= diff --git a/app/Events/Matrix/Base.php b/app/Events/Matrix/Base.php new file mode 100644 index 0000000..8b21013 --- /dev/null +++ b/app/Events/Matrix/Base.php @@ -0,0 +1,60 @@ +_data = json_decode(json_encode($request)); + } + + /** + * Enable getting values for keys in the response + * + * @note: This method is limited to certain values to ensure integrity reasons + * @note: Classes should return: + * + channel_id, + * + team_id, + * + ts, + * + user_id + * @param string $key + * @return mixed|object + * @throws ConnectionException + */ + public function __get(string $key) + { + switch ($key) { + case 'echoarea': + $rooms = collect(config('matrix.rooms')); + + return Echoarea::where('name',$rooms->get($this->room_id))->single(); + + case 'room': + $room_alias = Http::withToken(config('matrix.as_token')) + ->get(sprintf('%s/_matrix/client/v3/rooms/%s/state/m.room.canonical_alias',config('matrix.server'),$this->room_id)); + + return $room_alias->json('alias',$this->room_id); + + case 'topic': + $subject = Http::withToken(config('matrix.as_token')) + ->get(sprintf('%s/_matrix/client/v3/rooms/%s/state/m.room.topic',config('matrix.server'),$this->room_id)); + + return $subject->json('topic','Message from Matrix'); + + case 'room_id': + default: + return object_get($this->_data,$key); + } + } +} \ No newline at end of file diff --git a/app/Events/Matrix/Factory.php b/app/Events/Matrix/Factory.php new file mode 100644 index 0000000..e080173 --- /dev/null +++ b/app/Events/Matrix/Factory.php @@ -0,0 +1,50 @@ + Message::class, + ]; + + /** + * Returns new event instance + * + * @param string $type + * @param array $request + * @return Base + */ + public static function create(string $type,array $request): Base + { + $class = Arr::get(self::map,$type,Unknown::class); + Log::debug(sprintf('%s:- Working out Event Class for [%s] as [%s]',static::LOGKEY,$type,$class)); + + if (App::environment() == 'local') + file_put_contents('/tmp/event.'.$type,print_r($request,TRUE)); + + return new $class($request); + } + + public static function make(array $request): Base + { + // During the life of the event, this method is called twice - once during Middleware processing, and finally by the Controller. + static $o = NULL; + static $or = NULL; + + if (! $o OR ($or != $request)) { + $or = $request; + $o = self::create(Arr::get($request,'type','unknown'),$request); + } + + return $o; + } +} \ No newline at end of file diff --git a/app/Events/Matrix/Message.php b/app/Events/Matrix/Message.php new file mode 100644 index 0000000..2398b41 --- /dev/null +++ b/app/Events/Matrix/Message.php @@ -0,0 +1,52 @@ + 37 + * [content] => Array + * ( + * [body] => This is my text + * [m.mentions] => Array + * ( + * ) + * + * [msgtype] => m.text + * ) + * + * [event_id] => $fkpvy3qDkAGlB55nvqcH8mUfSxzELtaJ9TKJs6GP9us + * [origin_server_ts] => 1717917709298 + * [room_id] => !bbXofZepRYOhKjihLH:matrix.dege.au + * [sender] => @deon:matrix.dege.au + * [type] => m.room.message + * [unsigned] => Array + * ( + * [age] => 37 + * ) + * + * [user_id] => @deon:matrix.dege.au + * ) + */ +class Message extends Base +{ + public function __get($key) + { + switch ($key) { + case 'message': + return object_get($this->_data,'content.body'); + + case 'sender': + return object_get($this->_data,$key); + + case 'ts': + return object_get($this->_data,'origin_server_ts'); + + default: + return parent::__get($key); + } + } +} \ No newline at end of file diff --git a/app/Events/Matrix/Unknown.php b/app/Events/Matrix/Unknown.php new file mode 100644 index 0000000..a032810 --- /dev/null +++ b/app/Events/Matrix/Unknown.php @@ -0,0 +1,20 @@ +events,0,[])); + Log::info(sprintf('%s:- Dispatching Matrix Event [%s]',static::LOGKEY,get_class($event))); + event($event); + + return response(['result'=>'OK']); + } +} \ No newline at end of file diff --git a/app/Listeners/Matrix/MessageListener.php b/app/Listeners/Matrix/MessageListener.php new file mode 100644 index 0000000..d8e39be --- /dev/null +++ b/app/Listeners/Matrix/MessageListener.php @@ -0,0 +1,31 @@ +room_id,$event->sender)); + + Notification::route('echomail',$event->echoarea)->notify(new MatrixMessage($event)); + } +} \ No newline at end of file diff --git a/app/Notifications/Echomails/MatrixMessage.php b/app/Notifications/Echomails/MatrixMessage.php new file mode 100644 index 0000000..bfccf4e --- /dev/null +++ b/app/Notifications/Echomails/MatrixMessage.php @@ -0,0 +1,59 @@ +routeNotificationFor(static::via); + $o = $this->setupEchomail($echoarea); + + Log::info(sprintf('%s:+ Sending Matrix Message to [%s]',self::LOGKEY,$echoarea->name)); + + $our = our_address($echoarea->domain)->last(); + $o->to = 'All'; + $o->from = $this->mo->sender; + $o->datetime = Carbon::createFromTimestampMs($this->mo->ts); + $o->subject = $this->mo->topic; + $o->fftn_id = $our->id; + $o->kludges->put('CHRS:','UTF8 2'); + + // Message + $o->msg = $this->mo->message; + $o->set_origin = sprintf('Matrix %s (%s)',$this->mo->room,$our->ftn4d); + + $o->save(); + + return $o; + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c00eae8..932d10e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,7 +2,9 @@ namespace App\Providers; +use Illuminate\Http\Request; use Illuminate\Notifications\ChannelManager; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Notification; use Illuminate\Support\ServiceProvider; @@ -40,5 +42,9 @@ class AppServiceProvider extends ServiceProvider public function boot() { static::bootSingleOrFail(); + + Auth::viaRequest('matrix-token',function (Request $request) { + return (config('matrix.hs_token') && ($request->bearerToken() === config('matrix.hs_token'))) ? TRUE : NULL; + }); } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 6c64e52..9074bf2 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,10 +2,13 @@ namespace App\Providers; -use Illuminate\Support\Facades\Event; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use Illuminate\Support\Facades\Event; + +use App\Events\Matrix\Message; +use App\Listeners\Matrix\MessageListener; class EventServiceProvider extends ServiceProvider { @@ -29,6 +32,9 @@ class EventServiceProvider extends ServiceProvider { parent::boot(); - // + Event::listen( + Message::class, + MessageListener::class, + ); } } diff --git a/config/auth.php b/config/auth.php index db2d778..a12f814 100644 --- a/config/auth.php +++ b/config/auth.php @@ -2,136 +2,140 @@ return [ - /* - |-------------------------------------------------------------------------- - | Authentication Defaults - |-------------------------------------------------------------------------- - | - | This option controls the default authentication "guard" and password - | reset options for your application. You may change these defaults - | as required, but they're a perfect start for most applications. - | - */ + /* + |-------------------------------------------------------------------------- + | Authentication Defaults + |-------------------------------------------------------------------------- + | + | This option controls the default authentication "guard" and password + | reset options for your application. You may change these defaults + | as required, but they're a perfect start for most applications. + | + */ - 'defaults' => [ - 'guard' => 'web', - 'passwords' => 'users', - ], + 'defaults' => [ + 'guard' => 'web', + 'passwords' => 'users', + ], - /* - |-------------------------------------------------------------------------- - | Authentication Guards - |-------------------------------------------------------------------------- - | - | Next, you may define every authentication guard for your application. - | Of course, a great default configuration has been defined for you - | here which uses session storage and the Eloquent user provider. - | - | All authentication drivers have a user provider. This defines how the - | users are actually retrieved out of your database or other storage - | mechanisms used by this application to persist your user's data. - | - | Supported: "session", "token" - | - */ + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | here which uses session storage and the Eloquent user provider. + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | Supported: "session", "token" + | + */ - 'guards' => [ - 'web' => [ - 'driver' => 'session', - 'provider' => 'users', - ], + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], - 'api' => [ - 'driver' => 'sanctum', - 'provider' => 'users', - 'hash' => false, - ], + 'api' => [ + 'driver' => 'sanctum', + 'provider' => 'users', + 'hash' => false, + ], - 'token' => [ - 'driver' => 'token', - 'provider' => 'users', - 'hash' => false, - ], - ], + 'token' => [ + 'driver' => 'token', + 'provider' => 'users', + 'hash' => false, + ], - /* - |-------------------------------------------------------------------------- - | User Providers - |-------------------------------------------------------------------------- - | - | All authentication drivers have a user provider. This defines how the - | users are actually retrieved out of your database or other storage - | mechanisms used by this application to persist your user's data. - | - | If you have multiple user tables or models you may configure multiple - | sources which represent each model / table. These sources may then - | be assigned to any extra authentication guards you have defined. - | - | Supported: "database", "eloquent" - | - */ + 'matrix' => [ + 'driver' => 'matrix-token', + ] + ], - 'providers' => [ - 'users' => [ - 'driver' => 'eloquent', - 'model' => App\Models\User::class, - ], + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | If you have multiple user tables or models you may configure multiple + | sources which represent each model / table. These sources may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], - ], + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => App\Models\User::class, + ], - /* - |-------------------------------------------------------------------------- - | Resetting Passwords - |-------------------------------------------------------------------------- - | - | You may specify multiple password reset configurations if you have more - | than one user table or model in the application and you want to have - | separate password reset settings based on the specific user types. - | - | The expire time is the number of minutes that the reset token should be - | considered valid. This security feature keeps tokens short-lived so - | they have less time to be guessed. You may change this as needed. - | - */ + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], - 'passwords' => [ - 'users' => [ - 'provider' => 'users', - 'table' => 'password_resets', - 'expire' => 60, - 'throttle' => 60, - ], - ], + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | You may specify multiple password reset configurations if you have more + | than one user table or model in the application and you want to have + | separate password reset settings based on the specific user types. + | + | The expire time is the number of minutes that the reset token should be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + */ - /* - |-------------------------------------------------------------------------- - | Password Confirmation Timeout - |-------------------------------------------------------------------------- - | - | Here you may define the amount of seconds before a password confirmation - | times out and the user is prompted to re-enter their password via the - | confirmation screen. By default, the timeout lasts for three hours. - | - */ + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => 'password_resets', + 'expire' => 60, + 'throttle' => 60, + ], + ], - 'password_timeout' => 10800, + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | times out and the user is prompted to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ - /* - |-------------------------------------------------------------------------- - | Social Network Configuration - |-------------------------------------------------------------------------- - */ + 'password_timeout' => 10800, - 'social' => [ - 'w3id' => [ - 'name' => 'W3id', - 'id' => 'w3id', - 'class' => 'btn-primary', - 'icon' => 'fas fa-address-card', - ], - ], -]; + /* + |-------------------------------------------------------------------------- + | Social Network Configuration + |-------------------------------------------------------------------------- + */ + + 'social' => [ + 'w3id' => [ + 'name' => 'W3id', + 'id' => 'w3id', + 'class' => 'btn-primary', + 'icon' => 'fas fa-address-card', + ], + ], +]; \ No newline at end of file diff --git a/config/matrix.php b/config/matrix.php new file mode 100644 index 0000000..58c53b0 --- /dev/null +++ b/config/matrix.php @@ -0,0 +1,13 @@ + ENV('MATRIX_SERVER'), + 'as_token' => ENV('MATRIX_AS_TOKEN'), + 'hs_token' => ENV('MATRIX_HS_TOKEN'), + 'rooms' => [ + // '!bbXofZepRYOhKjihLH:matrix.dege.au' => 'PVT_TEST', + ] +]; diff --git a/routes/api.php b/routes/api.php index 9bf67e3..2981a25 100644 --- a/routes/api.php +++ b/routes/api.php @@ -2,7 +2,7 @@ use Illuminate\Support\Facades\Route; -use App\Http\Controllers\{DomainController,SystemController,ZoneController}; +use App\Http\Controllers\{DomainController,MatrixController}; /* |-------------------------------------------------------------------------- @@ -15,4 +15,8 @@ use App\Http\Controllers\{DomainController,SystemController,ZoneController}; | */ -Route::get('/domain/daily',[DomainController::class,'api_daily_stats']); \ No newline at end of file +Route::get('/domain/daily',[DomainController::class,'api_daily_stats']); + +Route::any('matrix/{item}',[MatrixController::class,'webhook']) + ->where('item', '.*') + ->middleware('auth:matrix'); \ No newline at end of file