├── .editorconfig
├── .env.example
├── .gitattributes
├── .gitignore
├── LICENSE
├── app
├── Activity.php
├── ActivityPub
│ └── HTTPSignature.php
├── Console
│ ├── Commands
│ │ ├── CreateUser.php
│ │ ├── Follow.php
│ │ ├── GeneratePublicKey.php
│ │ ├── GenerateToken.php
│ │ ├── ProcessInboxItem.php
│ │ └── SendProfileUpdate.php
│ └── Kernel.php
├── Events
│ ├── Event.php
│ ├── PostCreated.php
│ ├── PostDeleted.php
│ └── PostUpdated.php
├── Exceptions
│ └── Handler.php
├── Follower.php
├── Following.php
├── Http
│ ├── Controllers
│ │ ├── ActivityPubController.php
│ │ ├── Auth
│ │ │ ├── ForgotPasswordController.php
│ │ │ ├── LoginController.php
│ │ │ ├── RegisterController.php
│ │ │ └── ResetPasswordController.php
│ │ ├── Controller.php
│ │ ├── MicropubController.php
│ │ ├── PostController.php
│ │ ├── UserController.php
│ │ └── WebfingerController.php
│ ├── Kernel.php
│ └── Middleware
│ │ ├── CheckForMaintenanceMode.php
│ │ ├── EncryptCookies.php
│ │ ├── RedirectIfAuthenticated.php
│ │ ├── StartSession.php
│ │ ├── TrimStrings.php
│ │ ├── TrustProxies.php
│ │ └── VerifyCsrfToken.php
├── Inbox.php
├── Jobs
│ ├── ActivityPub
│ │ ├── Accept.php
│ │ ├── Create.php
│ │ ├── Follow.php
│ │ └── Undo.php
│ ├── ActivityPubHandler.php
│ └── DeliverActivity.php
├── Listeners
│ ├── SendPostDeletedToFollowers.php
│ ├── SendPostToFollowers.php
│ └── SendPostUpdatedToFollowers.php
├── Post.php
├── Profile.php
├── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── BroadcastServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
├── Token.php
└── User.php
├── artisan
├── bootstrap
├── app.php
└── cache
│ └── .gitignore
├── composer.json
├── composer.lock
├── config
├── app.php
├── auth.php
├── broadcasting.php
├── cache.php
├── database.php
├── filesystems.php
├── hashing.php
├── logging.php
├── mail.php
├── queue.php
├── services.php
├── session.php
└── view.php
├── database
├── .gitignore
├── factories
│ └── UserFactory.php
├── migrations
│ ├── 2014_10_12_000000_create_users_table.php
│ ├── 2014_10_12_100000_create_password_resets_table.php
│ ├── 2018_08_28_234721_user_public_key.php
│ ├── 2018_08_31_144339_activities.php
│ ├── 2018_08_31_150713_profiles.php
│ ├── 2018_08_31_164739_drop_activity_profile.php
│ ├── 2018_08_31_165136_create_inbox.php
│ ├── 2018_08_31_165717_profile_keys.php
│ ├── 2018_09_01_022939_inbox_actors.php
│ ├── 2018_09_01_023442_followers.php
│ ├── 2018_09_01_024922_profile_ids.php
│ ├── 2018_09_01_154424_tokens.php
│ ├── 2018_09_01_165420_posts.php
│ ├── 2018_09_01_172633_user_default_timezone.php
│ ├── 2018_09_01_192800_external_users.php
│ ├── 2018_09_01_222609_private_account.php
│ ├── 2018_09_01_222939_following.php
│ ├── 2018_09_01_230840_pinned_posts.php
│ ├── 2018_09_01_232403_user_images.php
│ ├── 2018_09_02_011659_user_bio.php
│ └── 2018_09_02_013254_user_keyvalues.php
└── seeds
│ └── DatabaseSeeder.php
├── package.json
├── phpunit.xml
├── public
├── .htaccess
├── css
│ └── app.css
├── favicon.ico
├── index.php
├── js
│ └── app.js
├── robots.txt
└── web.config
├── readme.md
├── resources
├── assets
│ ├── js
│ │ ├── app.js
│ │ ├── bootstrap.js
│ │ └── components
│ │ │ └── ExampleComponent.vue
│ └── sass
│ │ ├── _variables.scss
│ │ └── app.scss
├── lang
│ └── en
│ │ ├── auth.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ └── validation.php
└── views
│ └── welcome.blade.php
├── routes
├── api.php
├── channels.php
├── console.php
├── public.php
└── web.php
├── server.php
├── storage
├── app
│ ├── .gitignore
│ └── public
│ │ └── .gitignore
├── framework
│ ├── .gitignore
│ ├── cache
│ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ ├── testing
│ │ └── .gitignore
│ └── views
│ │ └── .gitignore
└── logs
│ └── .gitignore
├── tests
├── CreatesApplication.php
├── Feature
│ └── ExampleTest.php
├── TestCase.php
└── Unit
│ └── ExampleTest.php
└── webpack.mix.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 2
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.yml]
15 | indent_style = space
16 | indent_size = 2
17 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_URL=http://localhost
6 |
7 | LOG_CHANNEL=stack
8 |
9 | DB_CONNECTION=mysql
10 | DB_HOST=127.0.0.1
11 | DB_PORT=3306
12 | DB_DATABASE=homestead
13 | DB_USERNAME=homestead
14 | DB_PASSWORD=secret
15 |
16 | BROADCAST_DRIVER=log
17 | CACHE_DRIVER=file
18 | SESSION_DRIVER=file
19 | SESSION_LIFETIME=120
20 | QUEUE_DRIVER=database
21 |
22 | REDIS_HOST=127.0.0.1
23 | REDIS_PASSWORD=null
24 | REDIS_PORT=6379
25 |
26 | MAIL_DRIVER=smtp
27 | MAIL_HOST=smtp.mailtrap.io
28 | MAIL_PORT=2525
29 | MAIL_USERNAME=null
30 | MAIL_PASSWORD=null
31 | MAIL_ENCRYPTION=null
32 |
33 | PUSHER_APP_ID=
34 | PUSHER_APP_KEY=
35 | PUSHER_APP_SECRET=
36 | PUSHER_APP_CLUSTER=mt1
37 |
38 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
39 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
40 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.css linguist-vendored
3 | *.scss linguist-vendored
4 | *.js linguist-vendored
5 | CHANGELOG.md export-ignore
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /public/hot
3 | /public/storage
4 | /storage/*.key
5 | /vendor
6 | /.idea
7 | /.vscode
8 | /.vagrant
9 | Homestead.json
10 | Homestead.yaml
11 | npm-debug.log
12 | yarn-error.log
13 | .env
14 | .phpunit.result.cache
15 | .DS_Store
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/app/Activity.php:
--------------------------------------------------------------------------------
1 | belongsTo('\App\User');
12 | }
13 |
14 | public function setData($array) {
15 | $this->data = json_encode($array, JSON_UNESCAPED_SLASHES+JSON_PRETTY_PRINT);
16 | }
17 |
18 | public function toJSON($pretty=false) {
19 | $json = [
20 | '@context' => 'https://www.w3.org/ns/activitystreams',
21 | 'id' => env('APP_URL').'/activity/'.$this->id,
22 | 'type' => $this->type,
23 | 'actor' => $this->user->actorURL(),
24 | 'to' => ['https://www.w3.org/ns/activitystreams#Public'],
25 | ];
26 |
27 | $data = json_decode($this->data, true);
28 | $json = array_merge($json, $data);
29 |
30 | return json_encode($json, ($pretty ? JSON_PRETTY_PRINT : 0)+JSON_UNESCAPED_SLASHES);
31 | }
32 |
33 | public function sign(User &$user, $url) {
34 | $body = $this->toJSON();
35 |
36 | $headers = ActivityPub\HTTPSignature::sign($user, $url, $body);
37 |
38 | return $headers;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/ActivityPub/HTTPSignature.php:
--------------------------------------------------------------------------------
1 | private_key);
26 |
27 | openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
28 | $signature = base64_encode($signature);
29 |
30 | $signatureHeader = 'keyId="'.$user->actorURL().'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
31 |
32 | #Log::info('Signature: '.$signatureHeader);
33 |
34 | unset($headers['(request-target)']);
35 |
36 | $headers['Signature'] = $signatureHeader;
37 |
38 | return self::_headersToCurlArray($headers);
39 | }
40 |
41 | public static function parseSignatureHeader($signature) {
42 | $parts = explode(',', $signature);
43 | $signatureData = [];
44 |
45 | foreach($parts as $part) {
46 | if(preg_match('/(.+)="(.+)"/', $part, $match)) {
47 | $signatureData[$match[1]] = $match[2];
48 | }
49 | }
50 |
51 | if(!isset($signatureData['keyId'])) {
52 | return [
53 | 'error' => 'No keyId was found in the signature header. Found: '.implode(', ', array_keys($signatureData))
54 | ];
55 | }
56 |
57 | if(!\p3k\url\is_url($signatureData['keyId'])) {
58 | return [
59 | 'error' => 'keyId is not a URL: '.$signatureData['keyId']
60 | ];
61 | }
62 |
63 | if(!isset($signatureData['headers']) || !isset($signatureData['signature'])) {
64 | return [
65 | 'error' => 'Signature is missing headers or signature parts'
66 | ];
67 | }
68 |
69 | return $signatureData;
70 | }
71 |
72 | public static function verify($publicKey, $signatureData, $inputHeaders, $path, $body) {
73 | // TODO: Not sure how to determine the algorithm used, but everyone seems to use SHA256 right now
74 | $digest = 'SHA-256='.base64_encode(hash('sha256', $body, true));
75 |
76 | $headersToSign = [];
77 | foreach(explode(' ',$signatureData['headers']) as $h) {
78 | if($h == '(request-target)') {
79 | $headersToSign[$h] = 'post '.$path;
80 | } elseif($h == 'digest') {
81 | $headersToSign[$h] = $digest;
82 | } elseif(isset($inputHeaders[$h][0])) {
83 | $headersToSign[$h] = $inputHeaders[$h][0];
84 | }
85 | }
86 | $signingString = self::_headersToSigningString($headersToSign);
87 |
88 | $verified = openssl_verify($signingString, base64_decode($signatureData['signature']), $publicKey, OPENSSL_ALGO_SHA256);
89 |
90 | return [$verified, $signingString];
91 | }
92 |
93 | private static function _headersToSigningString($headers) {
94 | return implode("\n", array_map(function($k, $v){
95 | return strtolower($k).': '.$v;
96 | }, array_keys($headers), $headers));
97 | }
98 |
99 | private static function _headersToCurlArray($headers) {
100 | return array_map(function($k, $v){
101 | return "$k: $v";
102 | }, array_keys($headers), $headers);
103 | }
104 |
105 | private static function _digest($body) {
106 | return base64_encode(hash('sha256', $body, true));
107 | }
108 |
109 | protected static function _headersToSign($url, $digest=false) {
110 | $date = new DateTime('UTC');
111 |
112 | $headers = [
113 | '(request-target)' => 'post '.parse_url($url, PHP_URL_PATH),
114 | 'Date' => $date->format('D, d M Y H:i:s \G\M\T'),
115 | 'Host' => parse_url($url, PHP_URL_HOST),
116 | 'Content-Type' => 'application/activity+json',
117 | ];
118 |
119 | if($digest)
120 | $headers['Digest'] = 'SHA-256='.$digest;
121 |
122 | return $headers;
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/app/Console/Commands/CreateUser.php:
--------------------------------------------------------------------------------
1 | argument('username');
43 | $email = $this->argument('email');
44 | $name = $this->argument('name');
45 |
46 | $user = new User();
47 | $user->username = $username;
48 | $user->email = $email;
49 | $user->name = $name;
50 | $user->password = 'new';
51 | $user->default_timezone = 'America/Los_Angeles';
52 | $user->resetKeyPair();
53 | $user->save();
54 |
55 | $this->info("Created user");
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/Console/Commands/Follow.php:
--------------------------------------------------------------------------------
1 | argument('username');
44 | $url = $this->argument('url');
45 |
46 | $user = User::where('username', $username)->first();
47 |
48 | if(!$user) {
49 | $this->error('User not found: '.$username);
50 | return;
51 | }
52 |
53 | $this->info("Fetching $url");
54 |
55 | $profile = Profile::create($url);
56 |
57 | if(!$profile) {
58 | $this->error('Failed to fetch profile '.$url);
59 | return;
60 | }
61 |
62 | if(!$profile->inbox && !$profile->shared_inbox) {
63 | $this->error('Could not find inbox for '.$profile->profileid);
64 | return;
65 | }
66 |
67 | $followActivity = new Activity();
68 | $followActivity->type = 'Follow';
69 | $followActivity->user_id = $user->id;
70 | $followActivity->setData([
71 | 'object' => $profile->profileid
72 | ]);
73 | $followActivity->save();
74 |
75 | $following = new Following();
76 | $following->user_id = $user->id;
77 | $following->profile_id = $profile->id;
78 | $following->pending = true;
79 | $following->save();
80 |
81 | DeliverActivity::dispatch($followActivity, $profile->inbox);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/Console/Commands/GeneratePublicKey.php:
--------------------------------------------------------------------------------
1 | argument('username'))->first();
42 |
43 | if(!$user)
44 | $this->error('User not found');
45 |
46 | $user->resetKeyPair();
47 | $user->save();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/Console/Commands/GenerateToken.php:
--------------------------------------------------------------------------------
1 | argument('username'))->first();
42 |
43 | if(!$user)
44 | return $this->error('User not found');
45 |
46 | $token = new Token();
47 |
48 | $token->user_id = $user->id;
49 | $token->token = str_random(32);
50 | $token->scope = 'create update delete media';
51 | $token->client_id = env('APP_URL');
52 | $token->save();
53 |
54 | $this->info($token->token);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/Console/Commands/ProcessInboxItem.php:
--------------------------------------------------------------------------------
1 | argument('id'))->first();
32 |
33 | if(!$item)
34 | return $this->error('Item not found');
35 |
36 | $class = '\App\Jobs\ActivityPub\\'.$item->type;
37 | if(class_exists($class))
38 | $class::dispatch($item->id);
39 | else
40 | Log::error('Activity not supported: '.$item->type);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/Console/Commands/SendProfileUpdate.php:
--------------------------------------------------------------------------------
1 | argument('username');
34 | $inbox = $this->argument('inbox');
35 |
36 | $user = User::where('username', $username)->first();
37 |
38 | if(!$user) {
39 | $this->error('User not found: '.$username);
40 | return;
41 | }
42 |
43 | $activity = new Activity();
44 | $activity->type = 'Update';
45 | $activity->user_id = $user->id;
46 | $activity->setData([
47 | 'object' => $user->toActivityStreamsObject()
48 | ]);
49 | $activity->save();
50 |
51 | DeliverActivity::dispatch($activity, $inbox);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | command('inspire')
28 | // ->hourly();
29 | }
30 |
31 | /**
32 | * Register the commands for the application.
33 | *
34 | * @return void
35 | */
36 | protected function commands()
37 | {
38 | $this->load(__DIR__.'/Commands');
39 |
40 | require base_path('routes/console.php');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/Events/Event.php:
--------------------------------------------------------------------------------
1 | post = $post;
29 | }
30 |
31 | /**
32 | * Get the channels the event should broadcast on.
33 | *
34 | * @return \Illuminate\Broadcasting\Channel|array
35 | */
36 | public function broadcastOn()
37 | {
38 | return [];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Events/PostDeleted.php:
--------------------------------------------------------------------------------
1 | post = $post;
29 | }
30 |
31 | /**
32 | * Get the channels the event should broadcast on.
33 | *
34 | * @return \Illuminate\Broadcasting\Channel|array
35 | */
36 | public function broadcastOn()
37 | {
38 | return [];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Events/PostUpdated.php:
--------------------------------------------------------------------------------
1 | post = $post;
29 | }
30 |
31 | /**
32 | * Get the channels the event should broadcast on.
33 | *
34 | * @return \Illuminate\Broadcasting\Channel|array
35 | */
36 | public function broadcastOn()
37 | {
38 | return [];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | belongsTo('\App\Profile');
12 | }
13 |
14 | public function user() {
15 | return $this->belongsTo('\App\User');
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/app/Following.php:
--------------------------------------------------------------------------------
1 | belongsTo('\App\Profile');
12 | }
13 |
14 | public function user() {
15 | return $this->belongsTo('\App\User');
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ActivityPubController.php:
--------------------------------------------------------------------------------
1 | first();
16 |
17 | if(!$user) {
18 | return response()->json([
19 | 'error' => 'not_found'
20 | ], 404);
21 | }
22 |
23 | $this->user = $user;
24 |
25 | return true;
26 | }
27 |
28 | public function postInbox($username) {
29 | $check = $this->loadUser($username);
30 | if($check !== true)
31 | return $check;
32 |
33 | $body = Request::instance()->getContent();
34 |
35 | // Record in the DB even if it fails
36 | $inbox = new Inbox();
37 | $inbox->user_id = $this->user->id;
38 | $inbox->profile_id = 0;
39 | $inbox->type = Request::input('type', '');
40 | $inbox->verified = false;
41 | $inbox->data = json_encode(json_decode($body, true), JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES);
42 | $inbox->signature = Request::header('signature', '');
43 | $inbox->headers = '';
44 | $inbox->save();
45 |
46 | if(!Request::header('signature')) {
47 | return response()->json([
48 | 'error' => 'Missing Signature header'
49 | ], 400);
50 | }
51 |
52 | // Extract the signature properties
53 | $signatureData = HTTPSignature::parseSignatureHeader(Request::header('signature'));
54 |
55 | if(isset($signatureData['error']))
56 | return response()->json($signatureData, 400);
57 |
58 | // Check if we already know the key for this user
59 | $profile = Profile::where('keyid', $signatureData['keyId'])->first();
60 | if(!$profile) {
61 | $profile = Profile::createFromURL($signatureData['keyId']);
62 |
63 | if(!$profile) {
64 | return response()->json([
65 | 'error' => 'Failed to fetch profile for key "'.$signatureData['keyId'].'"'
66 | ], 400);
67 | }
68 |
69 | // Check that the keyId found at the profile matches the one in the signature
70 | if($profile->keyid != $signatureData['keyId']) {
71 | return response()->json([
72 | 'error' => 'Public key on profile did not match the keyId in the signature',
73 | 'signatureKeyId' => $signatureData['keyId'],
74 | 'profileKeyId' => $profile->keyid,
75 | ], 400);
76 | }
77 |
78 | $keyExisted = false;
79 | } else {
80 | $keyExisted = true;
81 | }
82 |
83 | $pkey = $profile->openssl_public_key();
84 | if(!$pkey) {
85 | return response()->json([
86 | 'error' => 'Error reading public key'
87 | ]);
88 | }
89 |
90 | $inputHeaders = Request::instance()->headers->all();
91 |
92 | list($verified, $headers) = HTTPSignature::verify($pkey, $signatureData, $inputHeaders, $this->user->inboxPath(), $body);
93 |
94 | // If the signature fails verification the first time, fetch the key and try again
95 | if($verified !== 1 && $keyExisted) {
96 | $profile->keyid = '';
97 | $profile->public_key = '';
98 | $profile->save();
99 |
100 | $profile = Profile::createFromURL($signatureData['keyId']);
101 |
102 | $pkey = $profile->openssl_public_key();
103 | if(!$pkey) {
104 | return response()->json([
105 | 'error' => 'Error reading public key'
106 | ]);
107 | }
108 |
109 | list($verified, $headers) = HTTPSignature::verify($pkey, $signatureData, $inputHeaders, $this->user->inboxPath(), $body);
110 | }
111 |
112 | $inbox->headers = $headers;
113 | $inbox->verified = $verified;
114 | $inbox->profile_id = $profile->id;
115 |
116 | if($verified !== 1) {
117 | $inbox->save();
118 | return response()->json([
119 | 'error' => 'Invalid signature',
120 | 'headers' => $headers,
121 | ], 400);
122 | }
123 |
124 | // HTTP signature checked out, make sure the "actor" of the activity matches that of the signature
125 |
126 | if(!Request::input('actor')) {
127 | $inbox->verified = 0;
128 | $inbox->save();
129 |
130 | return response()->json([
131 | 'error' => 'Request was missing an actor property'
132 | ], 400);
133 | }
134 |
135 | if(is_string(Request::input('actor'))) {
136 | $actor = Request::input('actor');
137 | } elseif(is_string(Request::input('actor.id'))) {
138 | $actor = Request::input('actor.id');
139 | } else {
140 | $inbox->verified = 0;
141 | $inbox->save();
142 |
143 | return response()->json([
144 | 'error' => 'The actor provided could not be parsed'
145 | ], 400);
146 | }
147 |
148 | $inbox->actor = $actor;
149 | $inbox->save();
150 |
151 | // Pass off to a handler based on the type of activity received
152 | $class = '\App\Jobs\ActivityPub\\'.$inbox->type;
153 | if(class_exists($class))
154 | $class::dispatch($inbox->id);
155 | else
156 | return response()->json('not supported: '.$inbox->type, 201);
157 |
158 | return response()->json('accepted', 202);
159 | }
160 |
161 | public function getOutbox($username) {
162 | $check = $this->loadUser($username);
163 | if($check !== true)
164 | return $check;
165 |
166 | return response()->json([
167 | '@context' => [
168 | 'https://www.w3.org/ns/activitystreams'
169 | ],
170 | 'id' => env('APP_URL').$this->user->outboxPath(),
171 | 'type' => 'OrderedCollection',
172 | 'totalItems' => $this->user->posts()->count(),
173 | ]);
174 | }
175 |
176 | public function featuredPosts($username) {
177 | $check = $this->loadUser($username);
178 | if($check !== true)
179 | return $check;
180 |
181 | $query = $this->user->posts()->where('pinned', true)
182 | ->orderByDesc('published')->limit(3)->get();
183 | $posts = [];
184 |
185 | foreach($query as $post) {
186 | $posts[] = $post->toActivityStreamsObject();
187 | }
188 |
189 | return response()->json([
190 | '@context' => [
191 | 'https://www.w3.org/ns/activitystreams'
192 | ],
193 | 'id' => env('APP_URL').$this->user->featuredPath(),
194 | 'type' => 'OrderedCollection',
195 | 'totalItems' => count($posts),
196 | 'orderedItems' => $posts,
197 | ]);
198 | }
199 |
200 | }
201 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ForgotPasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/LoginController.php:
--------------------------------------------------------------------------------
1 | middleware('guest')->except('logout');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisterController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ResetPasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | _error('unauthorized', 'No authorization header was present in the request', 401);
22 | }
23 |
24 | if(!preg_match('/^Bearer (.+)/', Request::header('authorization'), $match)) {
25 | return $this->_error('invalid_authorization', 'The authorization header was invalid', 400);
26 | }
27 |
28 | $token = $match[1];
29 | }
30 |
31 | $token = Token::where('token', $token)->first();
32 | if(!$token) {
33 | return $this->_error('invalid_access_token', 'The access token provided was not valid', 401);
34 | }
35 |
36 | $this->_user = $token->user;
37 |
38 | return $token;
39 | }
40 |
41 | public function post() {
42 | $token = $this->_verifyToken();
43 |
44 | if(get_class($token) != 'App\Token') {
45 | return $token; // error response
46 | }
47 |
48 | $request = \p3k\Micropub\Request::create(Request::all());
49 |
50 | if($request->error) {
51 | return $this->_error($request->error, $request->error_description, 400);
52 | }
53 |
54 | switch($request->action) {
55 | case 'create':
56 |
57 | $post = new Post();
58 | $post->user_id = $this->_user->id;
59 |
60 | $xray = new \p3k\XRay();
61 | $parsed = $xray->parse(env('APP_URL'), json_encode(['items'=>[$request->toMF2()]]));
62 |
63 | if(isset($parsed['data']))
64 | $post->raw = json_encode($parsed['data'], JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES);
65 | else
66 | $post->raw = json_encode($parsed, JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES);
67 |
68 | if(isset($request->properties['published'])) {
69 | try {
70 | $published = new DateTime($request->properties['published'][0]);
71 | } catch(\Exception $e) {
72 | return $this->_error('invalid_published_date', 'The published date provided was not valid', 400);
73 | }
74 | }
75 | else {
76 | $published = new DateTime();
77 | $published->setTimeZone(new DateTimeZone($this->_user->default_timezone));
78 | }
79 |
80 | $post->published = $published->format('Y-m-d H:i:s');
81 | $post->tz_offset = $published->format('Z');
82 |
83 | $post->save();
84 |
85 | Event::fire(new \App\Events\PostCreated($post));
86 |
87 | return response()->json([
88 | 'url' => $post->fullURL()
89 | ], 201)->header('Location', $post->fullURL());
90 |
91 | case 'update':
92 | case 'delete':
93 | default:
94 | return response()->json([
95 | 'error' => 'unsupported'
96 | ], 400);
97 | }
98 | }
99 |
100 | public function get() {
101 |
102 |
103 | return response()->json([
104 | 'token' => []
105 | ]);
106 | }
107 |
108 | protected function _error($type, $description, $code=400) {
109 | return response()->json(['error' => $type, 'error_description' => $description], $code)
110 | ->header('Access-Control-Allow-Origin', '*')
111 | ->header('Access-Control-Allow-Headers', 'Authorization');
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/app/Http/Controllers/PostController.php:
--------------------------------------------------------------------------------
1 | first();
15 |
16 | if(!$user) {
17 | return response()->json([
18 | 'error' => 'not_found'
19 | ], 404);
20 | }
21 |
22 | $post = Post::where('user_id', $user->id)
23 | ->where('id', $post_id)->first();
24 |
25 | if(!$post) {
26 | return response()->json([
27 | 'error' => 'not_found'
28 | ], 404);
29 | }
30 |
31 |
32 | // Switch on Accept header
33 | #if(request()->wantsJson()) {
34 | return response()->json($post->toActivityStreamsObject())
35 | ->header('Content-type', 'application/activity+json');
36 | #} else {
37 | # return view('profile', [
38 | # ]);
39 | #}
40 |
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/app/Http/Controllers/UserController.php:
--------------------------------------------------------------------------------
1 | get($username, 'json');
14 | }
15 |
16 | public function get($username, $format=false) {
17 |
18 | $user = User::where('username', $username)->first();
19 |
20 | if(!$user) {
21 | return response()->json([
22 | 'error' => 'not_found'
23 | ], 404);
24 | }
25 |
26 | // Switch on Accept header
27 | if((!$format && request()->wantsJson()) || $format == 'json') {
28 | $profile = $user->toActivityStreamsObject();
29 |
30 | if($user->external_domain) {
31 | // Add the Webfinger bits to this response
32 | $profile['---webfinger---'] = '---webfinger---';
33 | $profile['subject'] = 'acct:' . $user->username . '@' . $user->external_domain;
34 | $profile['links'] = [
35 | [
36 | 'rel' => 'self',
37 | 'type' => 'application/activity+json',
38 | 'href' => 'https://' . $user->external_domain . '/.well-known/user.json',
39 | ]
40 | ];
41 | }
42 |
43 | return response()->json($profile)->header('Content-type', 'application/activity+json');
44 | } else {
45 | if($user->external_domain) {
46 | return redirect('https://' . $user->external_domain . '/');
47 | } else {
48 | return $this->get($username, 'json');
49 | }
50 | }
51 |
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/app/Http/Controllers/WebfingerController.php:
--------------------------------------------------------------------------------
1 | json([
16 | 'error' => 'invalid_resource'
17 | ], 400);
18 | }
19 |
20 | $username = $match[1];
21 |
22 | $user = User::where('username', $username)->first();
23 |
24 | if(!$user) {
25 | return response()->json([
26 | 'error' => 'not_found'
27 | ], 404);
28 | }
29 |
30 | return response()->json([
31 | 'subject' => 'acct:'.$username.'@'.parse_url(env('APP_URL'), PHP_URL_HOST),
32 | 'aliases' => [
33 | env('APP_URL').'/'.$username,
34 | ],
35 | 'links' => [
36 | [
37 | 'rel' => 'self',
38 | 'type' => 'application/activity+json',
39 | 'href' => env('APP_URL').'/'.$username
40 | ]
41 | ]
42 | ]);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/Http/Kernel.php:
--------------------------------------------------------------------------------
1 | [
31 | \App\Http\Middleware\EncryptCookies::class,
32 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
33 | \Illuminate\Session\Middleware\StartSession::class,
34 | // \Illuminate\Session\Middleware\AuthenticateSession::class,
35 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
36 | \App\Http\Middleware\VerifyCsrfToken::class,
37 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
38 | ],
39 |
40 | 'public' => [
41 | \App\Http\Middleware\EncryptCookies::class,
42 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
43 | \App\Http\Middleware\StartSession::class,
44 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
45 | ],
46 |
47 | 'api' => [
48 | 'throttle:60,1',
49 | 'bindings',
50 | ],
51 | ];
52 |
53 | /**
54 | * The application's route middleware.
55 | *
56 | * These middleware may be assigned to groups or used individually.
57 | *
58 | * @var array
59 | */
60 | protected $routeMiddleware = [
61 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
62 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
63 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
64 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
65 | 'can' => \Illuminate\Auth\Middleware\Authorize::class,
66 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
67 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
68 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
69 | ];
70 | }
71 |
--------------------------------------------------------------------------------
/app/Http/Middleware/CheckForMaintenanceMode.php:
--------------------------------------------------------------------------------
1 | check()) {
21 | return redirect('/home');
22 | }
23 |
24 | return $next($request);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/Http/Middleware/StartSession.php:
--------------------------------------------------------------------------------
1 | belongsTo('\App\User');
14 | }
15 |
16 | public function profile() {
17 | return $this->belongsTo('\App\Profile');
18 | }
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/app/Jobs/ActivityPub/Accept.php:
--------------------------------------------------------------------------------
1 | _data->id);
12 |
13 | $data = json_decode($this->_data->data, true);
14 |
15 | if(!isset($data['object'])) {
16 | Log::error('No object was found in the Accept request');
17 | return;
18 | }
19 |
20 | if(!is_array($data['object'])) {
21 | Log::error('The "object" of the Accept request was not an object');
22 | return;
23 | }
24 |
25 | if(isset($data['object']['type']) && $data['object']['type'] == 'Follow') {
26 |
27 | $verify = $this->verifyObjectHost($data['object']['actor']);
28 | if(!$verify) {
29 | Log::error('Received an Accept Follow request for an object not on this website');
30 | return;
31 | }
32 |
33 | $following = Following::where('user_id', $this->_data->user_id)
34 | ->where('profile_id', $this->_data->profile_id)
35 | ->first();
36 |
37 | if($following) {
38 | Log::info('Profile '.$this->_data->profile_id.' accepted follow from '.$this->_user->username);
39 | $following->pending = false;
40 | $following->confirmed_at = date('Y-m-d H:i:s');
41 | $following->save();
42 | }
43 |
44 | return;
45 | }
46 |
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/Jobs/ActivityPub/Create.php:
--------------------------------------------------------------------------------
1 | _data->id);
12 |
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/Jobs/ActivityPub/Follow.php:
--------------------------------------------------------------------------------
1 | _data->id);
12 |
13 | $data = json_decode($this->_data->data, true);
14 |
15 | if(!isset($data['object'])) {
16 | Log::error('No object was found in the Follow request');
17 | return;
18 | }
19 |
20 | if(!is_string($data['object'])) {
21 | Log::error('The object of the Follow request was not a string');
22 | return;
23 | }
24 |
25 | $profile = Profile::where('id', $this->_data->profile_id)->first();
26 |
27 | $verify = $this->verifyObjectHost($data['object']);
28 | if(!$verify) {
29 | Log::error('Received a Follow request for an object not on this website');
30 | return;
31 | }
32 |
33 | $acceptFollow = true;
34 |
35 | if($this->_user->locked) {
36 | // Check if the user who requested to follow is already followed by this user
37 | if(!$this->_user->follows($profile)) {
38 | Log::warning('Received a Follow request from someone not in the whitelist: '.$profile->url);
39 | $acceptFollow = false;
40 | }
41 | }
42 |
43 | if($acceptFollow) {
44 | // Insert the follower record
45 | $follower = Follower::where('user_id', $this->_data->user_id)
46 | ->where('profile_id', $this->_data->profile_id)
47 | ->first();
48 | if(!$follower) {
49 | $follower = new Follower();
50 | $follower->user_id = $this->_data->user_id;
51 | $follower->profile_id = $this->_data->profile_id;
52 | }
53 | $follower->save();
54 | }
55 |
56 | // Send back the Accept/Reject
57 |
58 | $object = array_intersect_key($data, array_flip(['id','type','actor','object']));
59 |
60 | $responseActivity = new Activity();
61 | $responseActivity->type = ($acceptFollow ? 'Accept' : 'Reject');
62 | $responseActivity->user_id = $this->_data->user_id;
63 | $responseActivity->setData([
64 | 'object' => $object
65 | ]);
66 | $responseActivity->save();
67 |
68 | $payload = $responseActivity->toJSON();
69 |
70 | Log::info($payload);
71 | $headers = $responseActivity->sign($this->_user, $this->_data->profile->inbox);
72 |
73 | $ch = curl_init($this->_data->profile->inbox);
74 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
75 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
76 | curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
77 | curl_setopt($ch, CURLOPT_HEADER, true);
78 | $response = curl_exec($ch);
79 | Log::info('Inbox response: '.$response);
80 |
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/Jobs/ActivityPub/Undo.php:
--------------------------------------------------------------------------------
1 | _data->id);
12 |
13 | $data = json_decode($this->_data->data, true);
14 |
15 | if(!isset($data['object'])) {
16 | Log::error('No object was found in the Undo request');
17 | return;
18 | }
19 |
20 | if(!is_array($data['object'])) {
21 | Log::error('The "object" of the Undo request was not an object');
22 | return;
23 | }
24 |
25 | if(isset($data['object']['type']) && $data['object']['type'] == 'Follow') {
26 |
27 | $verify = $this->verifyObjectHost($data['object']['object']);
28 | if(!$verify) {
29 | Log::error('Received an Undo Follow request for an object not on this website');
30 | return;
31 | }
32 |
33 | $follower = Follower::where('user_id', $this->_data->user_id)
34 | ->where('profile_id', $this->_data->profile_id)
35 | ->first();
36 |
37 | if($follower) {
38 | Log::info('Profile '.$this->_data->profile_id.' unfollowed user '.$this->_user->username);
39 | $follower->delete();
40 | }
41 |
42 | return;
43 | }
44 |
45 | if(isset($data['object']['type']) && $data['object']['type'] == 'Create') {
46 |
47 | }
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/Jobs/ActivityPubHandler.php:
--------------------------------------------------------------------------------
1 | _data = Inbox::where('id', $inboxID)->first();
29 | $this->_user = User::where('id', $this->_data->user_id)->first();
30 | }
31 |
32 | protected function verifyObjectHost($object) {
33 | if(!\p3k\url\host_matches($object, env('APP_URL'))) {
34 | // Check if this is a follow request to a hosted account
35 | if(parse_url($object, PHP_URL_PATH) == '/.well-known/user.json') {
36 | $host = parse_url($object, PHP_URL_HOST);
37 | if($this->_user->external_domain != $host) {
38 | return false;
39 | }
40 | } else {
41 | return false;
42 | }
43 | }
44 |
45 | return true;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/app/Jobs/DeliverActivity.php:
--------------------------------------------------------------------------------
1 | activity = $activity;
28 | $this->inbox = $inbox;
29 | }
30 |
31 | /**
32 | * Execute the job.
33 | *
34 | * @return void
35 | */
36 | public function handle()
37 | {
38 | Log::info('Delivering activity '.$this->activity->id.' to inbox '.$this->inbox);
39 |
40 | $payload = $this->activity->toJSON();
41 |
42 | $user = $this->activity->user;
43 |
44 | Log::info($payload);
45 | $headers = $this->activity->sign($user, $this->inbox);
46 |
47 | $ch = curl_init($this->inbox);
48 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
49 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
50 | curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
51 | curl_setopt($ch, CURLOPT_HEADER, true);
52 | $response = curl_exec($ch);
53 | Log::info('Inbox response: '.$response);
54 |
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/Listeners/SendPostDeletedToFollowers.php:
--------------------------------------------------------------------------------
1 | post;
33 | $user = $post->user;
34 |
35 | Log::info('Post '.$post->fullURL().' was created, delivering to followers');
36 |
37 | $inboxes = [];
38 | foreach($user->followers as $follower) {
39 | if($follower->shared_inbox) {
40 | $inboxes[] = $follower->shared_inbox;
41 | } elseif($follower->inbox) {
42 | $inboxes[] = $follower->inbox;
43 | }
44 | }
45 | $inboxes = array_unique($inboxes);
46 |
47 | $activity = new Activity();
48 | $activity->type = 'Create';
49 | $activity->user_id = $user->id;
50 | $activity->setData([
51 | 'object' => $post->toActivityStreamsObject(),
52 | ]);
53 | $activity->save();
54 |
55 | foreach($inboxes as $inbox) {
56 | Log::info('Delivering to inbox '.$inbox);
57 | DeliverActivity::dispatch($activity, $inbox);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/Listeners/SendPostUpdatedToFollowers.php:
--------------------------------------------------------------------------------
1 | belongsTo('\App\User');
11 | }
12 |
13 | public function fullURL() {
14 | return env('APP_URL') . '/' . $this->user->username . '/' . $this->id;
15 | }
16 |
17 | public function published() {
18 | $tz = \p3k\date\tz_seconds_to_timezone($this->tz_offset);
19 | return new DateTime($this->published, $tz);
20 | }
21 |
22 | public function data() {
23 | return json_decode($this->raw, true);
24 | }
25 |
26 | public function toActivityStreamsObject() {
27 | $data = $this->data();
28 |
29 | $type = $data['post-type'] == 'article' ? 'Article' : 'Note';
30 |
31 | $object = [
32 | 'id' => $this->fullURL(),
33 | 'url' => $this->fullURL(),
34 | 'type' => $type,
35 | 'published' => $this->published()->format('c'),
36 | 'attributedTo' => $this->user->actorURL(),
37 | 'to' => ['https://www.w3.org/ns/activitystreams#Public'],
38 | ];
39 |
40 | if($this->user->external_domain) {
41 | $object['id'] = 'https://' . $this->user->external_domain . '/activitypub/' . $this->id;
42 | }
43 |
44 | if(isset($data['url']))
45 | $object['url'] = $data['url'];
46 |
47 | if(isset($data['content']))
48 | $object['content'] = $data['content']['text'];
49 |
50 | if(isset($data['name']))
51 | $object['name'] = $data['name'];
52 |
53 | // TODO: tags and mentions
54 |
55 |
56 | // TODO: photos and videos
57 |
58 |
59 | return $object;
60 | }
61 |
62 | // public function profile() {
63 | // return $this->belongsTo('\App\Profile');
64 | // }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/Profile.php:
--------------------------------------------------------------------------------
1 | hasMany('\App\Activity');
11 | }
12 |
13 | public function openssl_public_key() {
14 | return openssl_pkey_get_public($this->public_key);
15 | }
16 |
17 | public static function create($uri) {
18 | if(preg_match('/^http/', $uri))
19 | return Profile::createFromURL($uri);
20 | else
21 | return Profile::createFromWebfinger($uri);
22 | }
23 |
24 | public static function createFromURL($url) {
25 | Log::debug('Fetching profile '.$url);
26 | $ch = curl_init($url);
27 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
28 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
29 | curl_setopt($ch, CURLOPT_HTTPHEADER, [
30 | 'Accept: application/activity+json, application/json'
31 | ]);
32 | $response = curl_exec($ch);
33 | if($response) {
34 | $data = json_decode($response, true);
35 | if($data) {
36 | // Check that this looks like an activitypub profile
37 | if(isset($data['id']) && isset($data['type']) && in_array($data['type'], ['Person','Service'])) {
38 | return self::createFromProfile($data);
39 | }
40 | }
41 | }
42 |
43 | return false;
44 | }
45 |
46 | public static function createFromWebfinger($uri) {
47 | if(!preg_match('/(.+)@(.+)/', $uri, $match)) {
48 | return false;
49 | }
50 | $host = $match[2];
51 |
52 | Log::debug('Making Webfinger request for '.$uri);
53 | $ch = curl_init('http://'.$host.'/.well-known/webfinger?resource=acct:'.$uri);
54 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
55 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
56 | curl_setopt($ch, CURLOPT_HTTPHEADER, [
57 | 'Accept: application/activity+json, application/json'
58 | ]);
59 | $response = curl_exec($ch);
60 | if($response) {
61 | $data = json_decode($response, true);
62 | if($data) {
63 | if(isset($data['links'])) {
64 | foreach($data['links'] as $link) {
65 | if(isset($link['href']) && isset($link['rel']) && $link['rel'] == 'self'
66 | && isset($link['type']) && $link['type'] == 'application/activity+json') {
67 | return self::createFromURL($link['href']);
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
74 | return false;
75 | }
76 |
77 | public static function createFromProfile($data) {
78 | $profile = Profile::where('profileid', $data['id'])->first();
79 | if(!$profile) {
80 | $profile = new Profile();
81 | $profile->profileid = $data['id'];
82 | }
83 |
84 | if(isset($data['url']))
85 | $profile->url = $data['url'];
86 |
87 | if(isset($data['inbox']))
88 | $profile->inbox = $data['inbox'];
89 |
90 | if(isset($data['endpoints']['sharedInbox']))
91 | $profile->shared_inbox = $data['endpoints']['sharedInbox'];
92 |
93 | if(isset($data['preferredUsername']))
94 | $profile->username = $data['preferredUsername'];
95 |
96 | if(isset($data['name']))
97 | $profile->username = $data['name'];
98 |
99 | if(isset($data['icon']['url']))
100 | $profile->photo = $data['icon']['url'];
101 |
102 | if(isset($data['publicKey']) && isset($data['publicKey']['id']) && isset($data['publicKey']['publicKeyPem'])) {
103 | $profile->keyid = $data['publicKey']['id'];
104 | $profile->public_key = $data['publicKey']['publicKeyPem'];
105 | }
106 |
107 | $profile->data = json_encode($data, JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES);
108 | $profile->save();
109 | return $profile;
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | 'App\Policies\ModelPolicy',
17 | ];
18 |
19 | /**
20 | * Register any authentication / authorization services.
21 | *
22 | * @return void
23 | */
24 | public function boot()
25 | {
26 | $this->registerPolicies();
27 |
28 | //
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Providers/BroadcastServiceProvider.php:
--------------------------------------------------------------------------------
1 | [
17 | 'App\Listeners\SendPostToFollowers',
18 | ],
19 | 'App\Events\PostUpdated' => [
20 | 'App\Listeners\SendPostUpdatedToFollowers',
21 | ],
22 | 'App\Events\PostDeleted' => [
23 | 'App\Listeners\SendPostDeletedToFollowers',
24 | ],
25 | ];
26 |
27 | /**
28 | * Register any events for your application.
29 | *
30 | * @return void
31 | */
32 | public function boot()
33 | {
34 | parent::boot();
35 |
36 | //
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | mapApiRoutes();
39 |
40 | $this->mapWebRoutes();
41 |
42 | $this->mapPublicRoutes();
43 |
44 | //
45 | }
46 |
47 | /**
48 | * Define the "web" routes for the application.
49 | *
50 | * These routes all receive session state, CSRF protection, etc.
51 | *
52 | * @return void
53 | */
54 | protected function mapWebRoutes()
55 | {
56 | Route::middleware('web')
57 | ->namespace($this->namespace)
58 | ->group(base_path('routes/web.php'));
59 | }
60 |
61 | /**
62 | * Define the "api" routes for the application.
63 | *
64 | * These routes are typically stateless.
65 | *
66 | * @return void
67 | */
68 | protected function mapApiRoutes()
69 | {
70 | Route::prefix('api')
71 | ->middleware('api')
72 | ->namespace($this->namespace)
73 | ->group(base_path('routes/api.php'));
74 | }
75 |
76 |
77 | protected function mapPublicRoutes()
78 | {
79 | Route::middleware('public')
80 | ->namespace($this->namespace)
81 | ->group(base_path('routes/public.php'));
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/Token.php:
--------------------------------------------------------------------------------
1 | belongsTo('\App\User');
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/app/User.php:
--------------------------------------------------------------------------------
1 | "sha512",
33 | "private_key_bits" => 4096,
34 | "private_key_type" => OPENSSL_KEYTYPE_RSA,
35 | );
36 |
37 | $res = openssl_pkey_new($config);
38 |
39 | openssl_pkey_export($res, $privKey);
40 | $pubKey = openssl_pkey_get_details($res);
41 | $pubKey = $pubKey["key"];
42 |
43 | return [
44 | 'public' => $pubKey,
45 | 'private' => $privKey
46 | ];
47 | }
48 |
49 | public function resetKeyPair() {
50 | $key = User::generateNewKeyPair();
51 |
52 | $this->public_key = $key['public'];
53 | $this->private_key = $key['private'];
54 | }
55 |
56 |
57 | public function activities() {
58 | return $this->hasMany('\App\Activity');
59 | }
60 |
61 | public function posts() {
62 | return $this->hasMany('\App\Post');
63 | }
64 |
65 | public function followers() {
66 | return $this->belongsToMany('\App\Profile', 'followers')->using('App\Follower');
67 | }
68 |
69 | public function following() {
70 | return $this->belongsToMany('\App\Profile', 'following')->using('App\Following');
71 | }
72 |
73 | public function follows(Profile $profile) {
74 | return (bool)Following::where('user_id', $this->id)
75 | ->where('profile_id', $profile->id)
76 | ->where('pending', false)
77 | ->count();
78 | }
79 |
80 | public function actorURL() {
81 | if($this->external_domain) {
82 | $actor = 'https://' . $this->external_domain . '/.well-known/user.json';
83 | } else {
84 | $actor = env('APP_URL') . '/' . $this->username;
85 | }
86 |
87 | return $actor;
88 | }
89 |
90 | public function inboxPath() {
91 | return '/' . $this->username . '/inbox';
92 | }
93 |
94 | public function outboxPath() {
95 | return '/' . $this->username . '/outbox';
96 | }
97 |
98 | public function featuredPath() {
99 | return '/' . $this->username . '/featured';
100 | }
101 |
102 | public function toActivityStreamsObject() {
103 | $profile = [
104 | "@context" => [
105 | "https://www.w3.org/ns/activitystreams",
106 | "https://w3id.org/security/v1",
107 | ],
108 | "id" => $this->actorURL(),
109 | "type" => "Person",
110 | "preferredUsername" => $this->username,
111 | "url" => $this->actorURL(),
112 | "icon" => [
113 | "type" => "Image",
114 | "mediaType" => "image/jpeg",
115 | "url" => env('APP_URL')."/storage/images/".$this->username.".jpg",
116 | ],
117 | "inbox" => env('APP_URL').$this->inboxPath(),
118 | "outbox" => env('APP_URL').$this->outboxPath(),
119 | "featured" => env('APP_URL').$this->featuredPath(),
120 | "publicKey" => [
121 | "id" => $this->actorURL(),
122 | "owner" => $this->actorURL(),
123 | "publicKeyPem" => $this->public_key
124 | ]
125 | ];
126 |
127 | if($this->bio) {
128 | $profile['summary'] = $this->bio;
129 | }
130 |
131 | if($this->photo) {
132 | $profile['icon']['url'] = $this->photo;
133 | }
134 |
135 | if($this->banner) {
136 | $profile['image'] = [
137 | 'type' => 'image',
138 | 'mediaType' => 'image/jpeg',
139 | 'url' => $this->banner,
140 | ];
141 | }
142 |
143 | if($this->properties) {
144 | $profile['attachment'] = [];
145 | $properties = json_decode($this->properties, true);
146 | foreach($properties as $k=>$v) {
147 | $profile['attachment'][] = [
148 | 'type' => 'PropertyValue',
149 | 'name' => $k,
150 | 'value' => $v,
151 | ];
152 | }
153 | }
154 |
155 | if($this->external_domain) {
156 | // Override some of the properties
157 | $profile['url'] = 'https://' . $this->external_domain;
158 | }
159 |
160 | return $profile;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(Illuminate\Contracts\Console\Kernel::class);
34 |
35 | $status = $kernel->handle(
36 | $input = new Symfony\Component\Console\Input\ArgvInput,
37 | new Symfony\Component\Console\Output\ConsoleOutput
38 | );
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Shutdown The Application
43 | |--------------------------------------------------------------------------
44 | |
45 | | Once Artisan has finished running, we will fire off the shutdown events
46 | | so that any final work may be done by the application before we shut
47 | | down the process. This is the last thing to happen to the request.
48 | |
49 | */
50 |
51 | $kernel->terminate($input, $status);
52 |
53 | exit($status);
54 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | singleton(
30 | Illuminate\Contracts\Http\Kernel::class,
31 | App\Http\Kernel::class
32 | );
33 |
34 | $app->singleton(
35 | Illuminate\Contracts\Console\Kernel::class,
36 | App\Console\Kernel::class
37 | );
38 |
39 | $app->singleton(
40 | Illuminate\Contracts\Debug\ExceptionHandler::class,
41 | App\Exceptions\Handler::class
42 | );
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Return The Application
47 | |--------------------------------------------------------------------------
48 | |
49 | | This script returns the application instance. The instance is given to
50 | | the calling script so we can separate the building of the instances
51 | | from the actual running of the application and sending responses.
52 | |
53 | */
54 |
55 | return $app;
56 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/laravel",
3 | "description": "The Laravel Framework.",
4 | "keywords": ["framework", "laravel"],
5 | "license": "MIT",
6 | "type": "project",
7 | "require": {
8 | "php": "^7.1.3",
9 | "fideloper/proxy": "^4.0",
10 | "laravel/framework": "5.8.*",
11 | "laravel/tinker": "^1.0",
12 | "p3k/micropub": "^0.0",
13 | "p3k/utils": "^1.2",
14 | "p3k/xray": "^1.6",
15 | "predis/predis": "^1.1"
16 | },
17 | "require-dev": {
18 | "filp/whoops": "^2.0",
19 | "fzaninotto/faker": "^1.4",
20 | "mockery/mockery": "^1.0",
21 | "nunomaduro/collision": "^2.0",
22 | "phpunit/phpunit": "^7.0"
23 | },
24 | "autoload": {
25 | "classmap": [
26 | "database/seeds",
27 | "database/factories"
28 | ],
29 | "psr-4": {
30 | "App\\": "app/"
31 | }
32 | },
33 | "autoload-dev": {
34 | "psr-4": {
35 | "Tests\\": "tests/"
36 | }
37 | },
38 | "extra": {
39 | "laravel": {
40 | "dont-discover": [
41 | ]
42 | }
43 | },
44 | "scripts": {
45 | "post-root-package-install": [
46 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
47 | ],
48 | "post-create-project-cmd": [
49 | "@php artisan key:generate"
50 | ],
51 | "post-autoload-dump": [
52 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
53 | "@php artisan package:discover"
54 | ]
55 | },
56 | "config": {
57 | "preferred-install": "dist",
58 | "sort-packages": true,
59 | "optimize-autoloader": true
60 | },
61 | "minimum-stability": "dev",
62 | "prefer-stable": true
63 | }
64 |
--------------------------------------------------------------------------------
/config/app.php:
--------------------------------------------------------------------------------
1 | env('APP_NAME', 'Laravel'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Application Environment
21 | |--------------------------------------------------------------------------
22 | |
23 | | This value determines the "environment" your application is currently
24 | | running in. This may determine how you prefer to configure various
25 | | services your application utilizes. Set this in your ".env" file.
26 | |
27 | */
28 |
29 | 'env' => env('APP_ENV', 'production'),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Application Debug Mode
34 | |--------------------------------------------------------------------------
35 | |
36 | | When your application is in debug mode, detailed error messages with
37 | | stack traces will be shown on every error that occurs within your
38 | | application. If disabled, a simple generic error page is shown.
39 | |
40 | */
41 |
42 | 'debug' => env('APP_DEBUG', false),
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Application URL
47 | |--------------------------------------------------------------------------
48 | |
49 | | This URL is used by the console to properly generate URLs when using
50 | | the Artisan command line tool. You should set this to the root of
51 | | your application so that it is used when running Artisan tasks.
52 | |
53 | */
54 |
55 | 'url' => env('APP_URL', 'http://localhost'),
56 |
57 | /*
58 | |--------------------------------------------------------------------------
59 | | Application Timezone
60 | |--------------------------------------------------------------------------
61 | |
62 | | Here you may specify the default timezone for your application, which
63 | | will be used by the PHP date and date-time functions. We have gone
64 | | ahead and set this to a sensible default for you out of the box.
65 | |
66 | */
67 |
68 | 'timezone' => 'UTC',
69 |
70 | /*
71 | |--------------------------------------------------------------------------
72 | | Application Locale Configuration
73 | |--------------------------------------------------------------------------
74 | |
75 | | The application locale determines the default locale that will be used
76 | | by the translation service provider. You are free to set this value
77 | | to any of the locales which will be supported by the application.
78 | |
79 | */
80 |
81 | 'locale' => 'en',
82 |
83 | /*
84 | |--------------------------------------------------------------------------
85 | | Application Fallback Locale
86 | |--------------------------------------------------------------------------
87 | |
88 | | The fallback locale determines the locale to use when the current one
89 | | is not available. You may change the value to correspond to any of
90 | | the language folders that are provided through your application.
91 | |
92 | */
93 |
94 | 'fallback_locale' => 'en',
95 |
96 | /*
97 | |--------------------------------------------------------------------------
98 | | Encryption Key
99 | |--------------------------------------------------------------------------
100 | |
101 | | This key is used by the Illuminate encrypter service and should be set
102 | | to a random, 32 character string, otherwise these encrypted strings
103 | | will not be safe. Please do this before deploying an application!
104 | |
105 | */
106 |
107 | 'key' => env('APP_KEY'),
108 |
109 | 'cipher' => 'AES-256-CBC',
110 |
111 | /*
112 | |--------------------------------------------------------------------------
113 | | Autoloaded Service Providers
114 | |--------------------------------------------------------------------------
115 | |
116 | | The service providers listed here will be automatically loaded on the
117 | | request to your application. Feel free to add your own services to
118 | | this array to grant expanded functionality to your applications.
119 | |
120 | */
121 |
122 | 'providers' => [
123 |
124 | /*
125 | * Laravel Framework Service Providers...
126 | */
127 | Illuminate\Auth\AuthServiceProvider::class,
128 | Illuminate\Broadcasting\BroadcastServiceProvider::class,
129 | Illuminate\Bus\BusServiceProvider::class,
130 | Illuminate\Cache\CacheServiceProvider::class,
131 | Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
132 | Illuminate\Cookie\CookieServiceProvider::class,
133 | Illuminate\Database\DatabaseServiceProvider::class,
134 | Illuminate\Encryption\EncryptionServiceProvider::class,
135 | Illuminate\Filesystem\FilesystemServiceProvider::class,
136 | Illuminate\Foundation\Providers\FoundationServiceProvider::class,
137 | Illuminate\Hashing\HashServiceProvider::class,
138 | Illuminate\Mail\MailServiceProvider::class,
139 | Illuminate\Notifications\NotificationServiceProvider::class,
140 | Illuminate\Pagination\PaginationServiceProvider::class,
141 | Illuminate\Pipeline\PipelineServiceProvider::class,
142 | Illuminate\Queue\QueueServiceProvider::class,
143 | Illuminate\Redis\RedisServiceProvider::class,
144 | Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
145 | Illuminate\Session\SessionServiceProvider::class,
146 | Illuminate\Translation\TranslationServiceProvider::class,
147 | Illuminate\Validation\ValidationServiceProvider::class,
148 | Illuminate\View\ViewServiceProvider::class,
149 |
150 | /*
151 | * Package Service Providers...
152 | */
153 |
154 | /*
155 | * Application Service Providers...
156 | */
157 | App\Providers\AppServiceProvider::class,
158 | App\Providers\AuthServiceProvider::class,
159 | // App\Providers\BroadcastServiceProvider::class,
160 | App\Providers\EventServiceProvider::class,
161 | App\Providers\RouteServiceProvider::class,
162 |
163 | ],
164 |
165 | /*
166 | |--------------------------------------------------------------------------
167 | | Class Aliases
168 | |--------------------------------------------------------------------------
169 | |
170 | | This array of class aliases will be registered when this application
171 | | is started. However, feel free to register as many as you wish as
172 | | the aliases are "lazy" loaded so they don't hinder performance.
173 | |
174 | */
175 |
176 | 'aliases' => [
177 |
178 | 'App' => Illuminate\Support\Facades\App::class,
179 | 'Artisan' => Illuminate\Support\Facades\Artisan::class,
180 | 'Auth' => Illuminate\Support\Facades\Auth::class,
181 | 'Blade' => Illuminate\Support\Facades\Blade::class,
182 | 'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
183 | 'Bus' => Illuminate\Support\Facades\Bus::class,
184 | 'Cache' => Illuminate\Support\Facades\Cache::class,
185 | 'Config' => Illuminate\Support\Facades\Config::class,
186 | 'Cookie' => Illuminate\Support\Facades\Cookie::class,
187 | 'Crypt' => Illuminate\Support\Facades\Crypt::class,
188 | 'DB' => Illuminate\Support\Facades\DB::class,
189 | 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
190 | 'Event' => Illuminate\Support\Facades\Event::class,
191 | 'File' => Illuminate\Support\Facades\File::class,
192 | 'Gate' => Illuminate\Support\Facades\Gate::class,
193 | 'Hash' => Illuminate\Support\Facades\Hash::class,
194 | 'Lang' => Illuminate\Support\Facades\Lang::class,
195 | 'Log' => Illuminate\Support\Facades\Log::class,
196 | 'Mail' => Illuminate\Support\Facades\Mail::class,
197 | 'Notification' => Illuminate\Support\Facades\Notification::class,
198 | 'Password' => Illuminate\Support\Facades\Password::class,
199 | 'Queue' => Illuminate\Support\Facades\Queue::class,
200 | 'Redirect' => Illuminate\Support\Facades\Redirect::class,
201 | 'Redis' => Illuminate\Support\Facades\Redis::class,
202 | 'Request' => Illuminate\Support\Facades\Request::class,
203 | 'Response' => Illuminate\Support\Facades\Response::class,
204 | 'Route' => Illuminate\Support\Facades\Route::class,
205 | 'Schema' => Illuminate\Support\Facades\Schema::class,
206 | 'Session' => Illuminate\Support\Facades\Session::class,
207 | 'Storage' => Illuminate\Support\Facades\Storage::class,
208 | 'URL' => Illuminate\Support\Facades\URL::class,
209 | 'Validator' => Illuminate\Support\Facades\Validator::class,
210 | 'View' => Illuminate\Support\Facades\View::class,
211 |
212 | ],
213 |
214 | ];
215 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => 'web',
18 | 'passwords' => 'users',
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | here which uses session storage and the Eloquent user provider.
29 | |
30 | | All authentication drivers have a user provider. This defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | mechanisms used by this application to persist your user's data.
33 | |
34 | | Supported: "session", "token"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 |
44 | 'api' => [
45 | 'driver' => 'token',
46 | 'provider' => 'users',
47 | ],
48 | ],
49 |
50 | /*
51 | |--------------------------------------------------------------------------
52 | | User Providers
53 | |--------------------------------------------------------------------------
54 | |
55 | | All authentication drivers have a user provider. This defines how the
56 | | users are actually retrieved out of your database or other storage
57 | | mechanisms used by this application to persist your user's data.
58 | |
59 | | If you have multiple user tables or models you may configure multiple
60 | | sources which represent each model / table. These sources may then
61 | | be assigned to any extra authentication guards you have defined.
62 | |
63 | | Supported: "database", "eloquent"
64 | |
65 | */
66 |
67 | 'providers' => [
68 | 'users' => [
69 | 'driver' => 'eloquent',
70 | 'model' => App\User::class,
71 | ],
72 |
73 | // 'users' => [
74 | // 'driver' => 'database',
75 | // 'table' => 'users',
76 | // ],
77 | ],
78 |
79 | /*
80 | |--------------------------------------------------------------------------
81 | | Resetting Passwords
82 | |--------------------------------------------------------------------------
83 | |
84 | | You may specify multiple password reset configurations if you have more
85 | | than one user table or model in the application and you want to have
86 | | separate password reset settings based on the specific user types.
87 | |
88 | | The expire time is the number of minutes that the reset token should be
89 | | considered valid. This security feature keeps tokens short-lived so
90 | | they have less time to be guessed. You may change this as needed.
91 | |
92 | */
93 |
94 | 'passwords' => [
95 | 'users' => [
96 | 'provider' => 'users',
97 | 'table' => 'password_resets',
98 | 'expire' => 60,
99 | ],
100 | ],
101 |
102 | ];
103 |
--------------------------------------------------------------------------------
/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_DRIVER', 'null'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Broadcast Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the broadcast connections that will be used
26 | | to broadcast events to other systems or over websockets. Samples of
27 | | each available type of connection are provided inside this array.
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'pusher' => [
34 | 'driver' => 'pusher',
35 | 'key' => env('PUSHER_APP_KEY'),
36 | 'secret' => env('PUSHER_APP_SECRET'),
37 | 'app_id' => env('PUSHER_APP_ID'),
38 | 'options' => [
39 | 'cluster' => env('PUSHER_APP_CLUSTER'),
40 | 'encrypted' => true,
41 | ],
42 | ],
43 |
44 | 'redis' => [
45 | 'driver' => 'redis',
46 | 'connection' => 'default',
47 | ],
48 |
49 | 'log' => [
50 | 'driver' => 'log',
51 | ],
52 |
53 | 'null' => [
54 | 'driver' => 'null',
55 | ],
56 |
57 | ],
58 |
59 | ];
60 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_DRIVER', 'file'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Stores
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the cache "stores" for your application as
26 | | well as their drivers. You may even define multiple stores for the
27 | | same cache driver to group types of items stored in your caches.
28 | |
29 | */
30 |
31 | 'stores' => [
32 |
33 | 'apc' => [
34 | 'driver' => 'apc',
35 | ],
36 |
37 | 'array' => [
38 | 'driver' => 'array',
39 | ],
40 |
41 | 'database' => [
42 | 'driver' => 'database',
43 | 'table' => 'cache',
44 | 'connection' => null,
45 | ],
46 |
47 | 'file' => [
48 | 'driver' => 'file',
49 | 'path' => storage_path('framework/cache/data'),
50 | ],
51 |
52 | 'memcached' => [
53 | 'driver' => 'memcached',
54 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
55 | 'sasl' => [
56 | env('MEMCACHED_USERNAME'),
57 | env('MEMCACHED_PASSWORD'),
58 | ],
59 | 'options' => [
60 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
61 | ],
62 | 'servers' => [
63 | [
64 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
65 | 'port' => env('MEMCACHED_PORT', 11211),
66 | 'weight' => 100,
67 | ],
68 | ],
69 | ],
70 |
71 | 'redis' => [
72 | 'driver' => 'redis',
73 | 'connection' => 'default',
74 | ],
75 |
76 | ],
77 |
78 | /*
79 | |--------------------------------------------------------------------------
80 | | Cache Key Prefix
81 | |--------------------------------------------------------------------------
82 | |
83 | | When utilizing a RAM based store such as APC or Memcached, there might
84 | | be other applications utilizing the same cache. So, we'll specify a
85 | | value to get prefixed to all our keys so we can avoid collisions.
86 | |
87 | */
88 |
89 | 'prefix' => env(
90 | 'CACHE_PREFIX',
91 | str_slug(env('APP_NAME', 'laravel'), '_').'_cache'
92 | ),
93 |
94 | ];
95 |
--------------------------------------------------------------------------------
/config/database.php:
--------------------------------------------------------------------------------
1 | env('DB_CONNECTION', 'mysql'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Database Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here are each of the database connections setup for your application.
24 | | Of course, examples of configuring each database platform that is
25 | | supported by Laravel is shown below to make development simple.
26 | |
27 | |
28 | | All database work in Laravel is done through the PHP PDO facilities
29 | | so make sure you have the driver for your particular database of
30 | | choice installed on your machine before you begin development.
31 | |
32 | */
33 |
34 | 'connections' => [
35 |
36 | 'sqlite' => [
37 | 'driver' => 'sqlite',
38 | 'database' => env('DB_DATABASE', database_path('database.sqlite')),
39 | 'prefix' => '',
40 | ],
41 |
42 | 'mysql' => [
43 | 'driver' => 'mysql',
44 | 'host' => env('DB_HOST', '127.0.0.1'),
45 | 'port' => env('DB_PORT', '3306'),
46 | 'database' => env('DB_DATABASE', 'forge'),
47 | 'username' => env('DB_USERNAME', 'forge'),
48 | 'password' => env('DB_PASSWORD', ''),
49 | 'unix_socket' => env('DB_SOCKET', ''),
50 | 'charset' => 'utf8mb4',
51 | 'collation' => 'utf8mb4_unicode_ci',
52 | 'prefix' => '',
53 | 'strict' => true,
54 | 'engine' => null,
55 | ],
56 |
57 | 'pgsql' => [
58 | 'driver' => 'pgsql',
59 | 'host' => env('DB_HOST', '127.0.0.1'),
60 | 'port' => env('DB_PORT', '5432'),
61 | 'database' => env('DB_DATABASE', 'forge'),
62 | 'username' => env('DB_USERNAME', 'forge'),
63 | 'password' => env('DB_PASSWORD', ''),
64 | 'charset' => 'utf8',
65 | 'prefix' => '',
66 | 'schema' => 'public',
67 | 'sslmode' => 'prefer',
68 | ],
69 |
70 | 'sqlsrv' => [
71 | 'driver' => 'sqlsrv',
72 | 'host' => env('DB_HOST', 'localhost'),
73 | 'port' => env('DB_PORT', '1433'),
74 | 'database' => env('DB_DATABASE', 'forge'),
75 | 'username' => env('DB_USERNAME', 'forge'),
76 | 'password' => env('DB_PASSWORD', ''),
77 | 'charset' => 'utf8',
78 | 'prefix' => '',
79 | ],
80 |
81 | ],
82 |
83 | /*
84 | |--------------------------------------------------------------------------
85 | | Migration Repository Table
86 | |--------------------------------------------------------------------------
87 | |
88 | | This table keeps track of all the migrations that have already run for
89 | | your application. Using this information, we can determine which of
90 | | the migrations on disk haven't actually been run in the database.
91 | |
92 | */
93 |
94 | 'migrations' => 'migrations',
95 |
96 | /*
97 | |--------------------------------------------------------------------------
98 | | Redis Databases
99 | |--------------------------------------------------------------------------
100 | |
101 | | Redis is an open source, fast, and advanced key-value store that also
102 | | provides a richer set of commands than a typical key-value systems
103 | | such as APC or Memcached. Laravel makes it easy to dig right in.
104 | |
105 | */
106 |
107 | 'redis' => [
108 |
109 | 'client' => 'predis',
110 |
111 | 'default' => [
112 | 'host' => env('REDIS_HOST', '127.0.0.1'),
113 | 'password' => env('REDIS_PASSWORD', null),
114 | 'port' => env('REDIS_PORT', 6379),
115 | 'database' => 0,
116 | ],
117 |
118 | ],
119 |
120 | ];
121 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DRIVER', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Default Cloud Filesystem Disk
21 | |--------------------------------------------------------------------------
22 | |
23 | | Many applications store files both locally and in the cloud. For this
24 | | reason, you may specify a default "cloud" driver here. This driver
25 | | will be bound as the Cloud disk implementation in the container.
26 | |
27 | */
28 |
29 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Filesystem Disks
34 | |--------------------------------------------------------------------------
35 | |
36 | | Here you may configure as many filesystem "disks" as you wish, and you
37 | | may even configure multiple disks of the same driver. Defaults have
38 | | been setup for each driver as an example of the required options.
39 | |
40 | | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace"
41 | |
42 | */
43 |
44 | 'disks' => [
45 |
46 | 'local' => [
47 | 'driver' => 'local',
48 | 'root' => storage_path('app'),
49 | ],
50 |
51 | 'public' => [
52 | 'driver' => 'local',
53 | 'root' => storage_path('app/public'),
54 | 'url' => env('APP_URL').'/storage',
55 | 'visibility' => 'public',
56 | ],
57 |
58 | 's3' => [
59 | 'driver' => 's3',
60 | 'key' => env('AWS_ACCESS_KEY_ID'),
61 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
62 | 'region' => env('AWS_DEFAULT_REGION'),
63 | 'bucket' => env('AWS_BUCKET'),
64 | 'url' => env('AWS_URL'),
65 | ],
66 |
67 | ],
68 |
69 | ];
70 |
--------------------------------------------------------------------------------
/config/hashing.php:
--------------------------------------------------------------------------------
1 | 'bcrypt',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Bcrypt Options
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may specify the configuration options that should be used when
26 | | passwords are hashed using the Bcrypt algorithm. This will allow you
27 | | to control the amount of time it takes to hash the given password.
28 | |
29 | */
30 |
31 | 'bcrypt' => [
32 | 'rounds' => env('BCRYPT_ROUNDS', 10),
33 | ],
34 |
35 | /*
36 | |--------------------------------------------------------------------------
37 | | Argon Options
38 | |--------------------------------------------------------------------------
39 | |
40 | | Here you may specify the configuration options that should be used when
41 | | passwords are hashed using the Argon algorithm. These will allow you
42 | | to control the amount of time it takes to hash the given password.
43 | |
44 | */
45 |
46 | 'argon' => [
47 | 'memory' => 1024,
48 | 'threads' => 2,
49 | 'time' => 2,
50 | ],
51 |
52 | ];
53 |
--------------------------------------------------------------------------------
/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Log Channels
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may configure the log channels for your application. Out of
26 | | the box, Laravel uses the Monolog PHP logging library. This gives
27 | | you a variety of powerful log handlers / formatters to utilize.
28 | |
29 | | Available Drivers: "single", "daily", "slack", "syslog",
30 | | "errorlog", "monolog",
31 | | "custom", "stack"
32 | |
33 | */
34 |
35 | 'channels' => [
36 | 'stack' => [
37 | 'driver' => 'stack',
38 | 'channels' => ['single'],
39 | ],
40 |
41 | 'single' => [
42 | 'driver' => 'single',
43 | 'path' => storage_path('logs/laravel.log'),
44 | 'level' => 'debug',
45 | ],
46 |
47 | 'daily' => [
48 | 'driver' => 'daily',
49 | 'path' => storage_path('logs/laravel.log'),
50 | 'level' => 'debug',
51 | 'days' => 7,
52 | ],
53 |
54 | 'slack' => [
55 | 'driver' => 'slack',
56 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
57 | 'username' => 'Laravel Log',
58 | 'emoji' => ':boom:',
59 | 'level' => 'critical',
60 | ],
61 |
62 | 'stderr' => [
63 | 'driver' => 'monolog',
64 | 'handler' => StreamHandler::class,
65 | 'with' => [
66 | 'stream' => 'php://stderr',
67 | ],
68 | ],
69 |
70 | 'syslog' => [
71 | 'driver' => 'syslog',
72 | 'level' => 'debug',
73 | ],
74 |
75 | 'errorlog' => [
76 | 'driver' => 'errorlog',
77 | 'level' => 'debug',
78 | ],
79 | ],
80 |
81 | ];
82 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_DRIVER', 'smtp'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | SMTP Host Address
24 | |--------------------------------------------------------------------------
25 | |
26 | | Here you may provide the host address of the SMTP server used by your
27 | | applications. A default option is provided that is compatible with
28 | | the Mailgun mail service which will provide reliable deliveries.
29 | |
30 | */
31 |
32 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | SMTP Host Port
37 | |--------------------------------------------------------------------------
38 | |
39 | | This is the SMTP port used by your application to deliver e-mails to
40 | | users of the application. Like the host we have set this value to
41 | | stay compatible with the Mailgun e-mail application by default.
42 | |
43 | */
44 |
45 | 'port' => env('MAIL_PORT', 587),
46 |
47 | /*
48 | |--------------------------------------------------------------------------
49 | | Global "From" Address
50 | |--------------------------------------------------------------------------
51 | |
52 | | You may wish for all e-mails sent by your application to be sent from
53 | | the same address. Here, you may specify a name and address that is
54 | | used globally for all e-mails that are sent by your application.
55 | |
56 | */
57 |
58 | 'from' => [
59 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
60 | 'name' => env('MAIL_FROM_NAME', 'Example'),
61 | ],
62 |
63 | /*
64 | |--------------------------------------------------------------------------
65 | | E-Mail Encryption Protocol
66 | |--------------------------------------------------------------------------
67 | |
68 | | Here you may specify the encryption protocol that should be used when
69 | | the application send e-mail messages. A sensible default using the
70 | | transport layer security protocol should provide great security.
71 | |
72 | */
73 |
74 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
75 |
76 | /*
77 | |--------------------------------------------------------------------------
78 | | SMTP Server Username
79 | |--------------------------------------------------------------------------
80 | |
81 | | If your SMTP server requires a username for authentication, you should
82 | | set it here. This will get used to authenticate with your server on
83 | | connection. You may also set the "password" value below this one.
84 | |
85 | */
86 |
87 | 'username' => env('MAIL_USERNAME'),
88 |
89 | 'password' => env('MAIL_PASSWORD'),
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Sendmail System Path
94 | |--------------------------------------------------------------------------
95 | |
96 | | When using the "sendmail" driver to send e-mails, we will need to know
97 | | the path to where Sendmail lives on this server. A default path has
98 | | been provided here, which will work well on most of your systems.
99 | |
100 | */
101 |
102 | 'sendmail' => '/usr/sbin/sendmail -bs',
103 |
104 | /*
105 | |--------------------------------------------------------------------------
106 | | Markdown Mail Settings
107 | |--------------------------------------------------------------------------
108 | |
109 | | If you are using Markdown based email rendering, you may configure your
110 | | theme and component paths here, allowing you to customize the design
111 | | of the emails. Or, you may simply stick with the Laravel defaults!
112 | |
113 | */
114 |
115 | 'markdown' => [
116 | 'theme' => 'default',
117 |
118 | 'paths' => [
119 | resource_path('views/vendor/mail'),
120 | ],
121 | ],
122 |
123 | ];
124 |
--------------------------------------------------------------------------------
/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_DRIVER', 'sync'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection information for each server that
24 | | is used by your application. A default configuration has been added
25 | | for each back-end shipped with Laravel. You are free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'table' => 'jobs',
40 | 'queue' => 'default',
41 | 'retry_after' => 90,
42 | ],
43 |
44 | 'beanstalkd' => [
45 | 'driver' => 'beanstalkd',
46 | 'host' => 'localhost',
47 | 'queue' => 'default',
48 | 'retry_after' => 90,
49 | ],
50 |
51 | 'sqs' => [
52 | 'driver' => 'sqs',
53 | 'key' => env('SQS_KEY', 'your-public-key'),
54 | 'secret' => env('SQS_SECRET', 'your-secret-key'),
55 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
56 | 'queue' => env('SQS_QUEUE', 'your-queue-name'),
57 | 'region' => env('SQS_REGION', 'us-east-1'),
58 | ],
59 |
60 | 'redis' => [
61 | 'driver' => 'redis',
62 | 'connection' => 'default',
63 | 'queue' => 'default',
64 | 'retry_after' => 90,
65 | 'block_for' => null,
66 | ],
67 |
68 | ],
69 |
70 | /*
71 | |--------------------------------------------------------------------------
72 | | Failed Queue Jobs
73 | |--------------------------------------------------------------------------
74 | |
75 | | These options configure the behavior of failed queue job logging so you
76 | | can control which database and table are used to store the jobs that
77 | | have failed. You may change them to any database / table you wish.
78 | |
79 | */
80 |
81 | 'failed' => [
82 | 'database' => env('DB_CONNECTION', 'mysql'),
83 | 'table' => 'failed_jobs',
84 | ],
85 |
86 | ];
87 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'domain' => env('MAILGUN_DOMAIN'),
19 | 'secret' => env('MAILGUN_SECRET'),
20 | ],
21 |
22 | 'ses' => [
23 | 'key' => env('SES_KEY'),
24 | 'secret' => env('SES_SECRET'),
25 | 'region' => env('SES_REGION', 'us-east-1'),
26 | ],
27 |
28 | 'sparkpost' => [
29 | 'secret' => env('SPARKPOST_SECRET'),
30 | ],
31 |
32 | 'stripe' => [
33 | 'model' => App\User::class,
34 | 'key' => env('STRIPE_KEY'),
35 | 'secret' => env('STRIPE_SECRET'),
36 | ],
37 |
38 | ];
39 |
--------------------------------------------------------------------------------
/config/session.php:
--------------------------------------------------------------------------------
1 | env('SESSION_DRIVER', 'file'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Session Lifetime
24 | |--------------------------------------------------------------------------
25 | |
26 | | Here you may specify the number of minutes that you wish the session
27 | | to be allowed to remain idle before it expires. If you want them
28 | | to immediately expire on the browser closing, set that option.
29 | |
30 | */
31 |
32 | 'lifetime' => env('SESSION_LIFETIME', 120),
33 |
34 | 'expire_on_close' => false,
35 |
36 | /*
37 | |--------------------------------------------------------------------------
38 | | Session Encryption
39 | |--------------------------------------------------------------------------
40 | |
41 | | This option allows you to easily specify that all of your session data
42 | | should be encrypted before it is stored. All encryption will be run
43 | | automatically by Laravel and you can use the Session like normal.
44 | |
45 | */
46 |
47 | 'encrypt' => false,
48 |
49 | /*
50 | |--------------------------------------------------------------------------
51 | | Session File Location
52 | |--------------------------------------------------------------------------
53 | |
54 | | When using the native session driver, we need a location where session
55 | | files may be stored. A default has been set for you but a different
56 | | location may be specified. This is only needed for file sessions.
57 | |
58 | */
59 |
60 | 'files' => storage_path('framework/sessions'),
61 |
62 | /*
63 | |--------------------------------------------------------------------------
64 | | Session Database Connection
65 | |--------------------------------------------------------------------------
66 | |
67 | | When using the "database" or "redis" session drivers, you may specify a
68 | | connection that should be used to manage these sessions. This should
69 | | correspond to a connection in your database configuration options.
70 | |
71 | */
72 |
73 | 'connection' => null,
74 |
75 | /*
76 | |--------------------------------------------------------------------------
77 | | Session Database Table
78 | |--------------------------------------------------------------------------
79 | |
80 | | When using the "database" session driver, you may specify the table we
81 | | should use to manage the sessions. Of course, a sensible default is
82 | | provided for you; however, you are free to change this as needed.
83 | |
84 | */
85 |
86 | 'table' => 'sessions',
87 |
88 | /*
89 | |--------------------------------------------------------------------------
90 | | Session Cache Store
91 | |--------------------------------------------------------------------------
92 | |
93 | | When using the "apc" or "memcached" session drivers, you may specify a
94 | | cache store that should be used for these sessions. This value must
95 | | correspond with one of the application's configured cache stores.
96 | |
97 | */
98 |
99 | 'store' => null,
100 |
101 | /*
102 | |--------------------------------------------------------------------------
103 | | Session Sweeping Lottery
104 | |--------------------------------------------------------------------------
105 | |
106 | | Some session drivers must manually sweep their storage location to get
107 | | rid of old sessions from storage. Here are the chances that it will
108 | | happen on a given request. By default, the odds are 2 out of 100.
109 | |
110 | */
111 |
112 | 'lottery' => [2, 100],
113 |
114 | /*
115 | |--------------------------------------------------------------------------
116 | | Session Cookie Name
117 | |--------------------------------------------------------------------------
118 | |
119 | | Here you may change the name of the cookie used to identify a session
120 | | instance by ID. The name specified here will get used every time a
121 | | new session cookie is created by the framework for every driver.
122 | |
123 | */
124 |
125 | 'cookie' => env(
126 | 'SESSION_COOKIE',
127 | str_slug(env('APP_NAME', 'laravel'), '_').'_session'
128 | ),
129 |
130 | /*
131 | |--------------------------------------------------------------------------
132 | | Session Cookie Path
133 | |--------------------------------------------------------------------------
134 | |
135 | | The session cookie path determines the path for which the cookie will
136 | | be regarded as available. Typically, this will be the root path of
137 | | your application but you are free to change this when necessary.
138 | |
139 | */
140 |
141 | 'path' => '/',
142 |
143 | /*
144 | |--------------------------------------------------------------------------
145 | | Session Cookie Domain
146 | |--------------------------------------------------------------------------
147 | |
148 | | Here you may change the domain of the cookie used to identify a session
149 | | in your application. This will determine which domains the cookie is
150 | | available to in your application. A sensible default has been set.
151 | |
152 | */
153 |
154 | 'domain' => env('SESSION_DOMAIN', null),
155 |
156 | /*
157 | |--------------------------------------------------------------------------
158 | | HTTPS Only Cookies
159 | |--------------------------------------------------------------------------
160 | |
161 | | By setting this option to true, session cookies will only be sent back
162 | | to the server if the browser has a HTTPS connection. This will keep
163 | | the cookie from being sent to you if it can not be done securely.
164 | |
165 | */
166 |
167 | 'secure' => env('SESSION_SECURE_COOKIE', false),
168 |
169 | /*
170 | |--------------------------------------------------------------------------
171 | | HTTP Access Only
172 | |--------------------------------------------------------------------------
173 | |
174 | | Setting this value to true will prevent JavaScript from accessing the
175 | | value of the cookie and the cookie will only be accessible through
176 | | the HTTP protocol. You are free to modify this option if needed.
177 | |
178 | */
179 |
180 | 'http_only' => true,
181 |
182 | /*
183 | |--------------------------------------------------------------------------
184 | | Same-Site Cookies
185 | |--------------------------------------------------------------------------
186 | |
187 | | This option determines how your cookies behave when cross-site requests
188 | | take place, and can be used to mitigate CSRF attacks. By default, we
189 | | do not enable this as other CSRF protection services are in place.
190 | |
191 | | Supported: "lax", "strict"
192 | |
193 | */
194 |
195 | 'same_site' => null,
196 |
197 | ];
198 |
--------------------------------------------------------------------------------
/config/view.php:
--------------------------------------------------------------------------------
1 | [
17 | resource_path('views'),
18 | ],
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Compiled View Path
23 | |--------------------------------------------------------------------------
24 | |
25 | | This option determines where all the compiled Blade templates will be
26 | | stored for your application. Typically, this is within the storage
27 | | directory. However, as usual, you are free to change this value.
28 | |
29 | */
30 |
31 | 'compiled' => realpath(storage_path('framework/views')),
32 |
33 | ];
34 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite
2 |
--------------------------------------------------------------------------------
/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 | define(App\User::class, function (Faker $faker) {
17 | return [
18 | 'name' => $faker->name,
19 | 'email' => $faker->unique()->safeEmail,
20 | 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
21 | 'remember_token' => str_random(10),
22 | ];
23 | });
24 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('name');
19 | $table->string('username')->unique();
20 | $table->string('email')->unique();
21 | $table->string('password');
22 | $table->rememberToken();
23 | $table->timestamps();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('users');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_100000_create_password_resets_table.php:
--------------------------------------------------------------------------------
1 | string('email')->index();
18 | $table->string('token');
19 | $table->timestamp('created_at')->nullable();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::dropIfExists('password_resets');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_08_28_234721_user_public_key.php:
--------------------------------------------------------------------------------
1 | longtext('public_key');
18 | $table->longtext('private_key');
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::table('users', function (Blueprint $table) {
30 | $table->dropcolumn('public_key');
31 | $table->dropcolumn('private_key');
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/database/migrations/2018_08_31_144339_activities.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->timestamps();
19 | $table->integer('user_id')->nullable();
20 | $table->integer('profile_id')->nullable();
21 | $table->string('type', 255);
22 | $table->longtext('data');
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('activities');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/database/migrations/2018_08_31_150713_profiles.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->timestamps();
19 | $table->string('url', 512)->unique();
20 | $table->string('username', 255);
21 | $table->string('name', 255)->default('');
22 | $table->string('photo', 512)->default('');
23 | $table->string('inbox', 512)->default('');
24 | $table->string('shared_inbox', 512)->default('');
25 | $table->longtext('data')->nullable();
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function down()
35 | {
36 | Schema::dropIfExists('profiles');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/database/migrations/2018_08_31_164739_drop_activity_profile.php:
--------------------------------------------------------------------------------
1 | dropColumn('profile_id');
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('activities', function (Blueprint $table) {
29 | $table->integer('profile_id')->nullable();
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_08_31_165136_create_inbox.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->timestamps();
19 | $table->integer('user_id');
20 | $table->integer('profile_id');
21 | $table->string('type', 255);
22 | $table->boolean('verified')->default(false);
23 | $table->longtext('data');
24 | $table->longtext('signature');
25 | $table->longtext('headers');
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function down()
35 | {
36 | Schema::dropIfExists('inbox');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/database/migrations/2018_08_31_165717_profile_keys.php:
--------------------------------------------------------------------------------
1 | string('keyid', 255)->default('');
18 | $table->longtext('public_key')->nullable();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::table('profiles', function (Blueprint $table) {
30 | $table->dropColumn('keyid');
31 | $table->dropColumn('public_key');
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_022939_inbox_actors.php:
--------------------------------------------------------------------------------
1 | string('actor', 512)->default('');
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('inbox', function (Blueprint $table) {
29 | $table->dropColumn('actor');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_023442_followers.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->timestamps();
19 | $table->integer('user_id');
20 | $table->integer('profile_id');
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | *
27 | * @return void
28 | */
29 | public function down()
30 | {
31 | Schema::dropIfExists('followers');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_024922_profile_ids.php:
--------------------------------------------------------------------------------
1 | string('profileid', 512)->default('');
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('profiles', function (Blueprint $table) {
29 | $table->dropColumn('profileid');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_154424_tokens.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('token', 255)->index();
19 | $table->integer('user_id');
20 | $table->string('scope', 255)->default('');
21 | $table->string('client_id', 512)->nullable();
22 | $table->string('redirect_uri', 512)->nullable();
23 | $table->timestamps();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('tokens');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_165420_posts.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->integer('user_id')->nullable()->index();
19 | $table->integer('profile_id')->nullable();
20 | $table->timestamps();
21 | $table->dateTime('published')->index();
22 | $table->integer('tz_offset')->default(0);
23 | $table->string('name', 512)->nullable();
24 | $table->longtext('content')->nullable();
25 | $table->longtext('raw')->nullable();
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function down()
35 | {
36 | Schema::dropIfExists('posts');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_172633_user_default_timezone.php:
--------------------------------------------------------------------------------
1 | string('default_timezone', 100)->default('America/Los_Angeles');
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('users', function (Blueprint $table) {
29 | $table->dropcolumn('default_timezone');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_192800_external_users.php:
--------------------------------------------------------------------------------
1 | string('external_domain', 512)->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('users', function (Blueprint $table) {
29 | $table->dropcolumn('external_domain');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_222609_private_account.php:
--------------------------------------------------------------------------------
1 | boolean('locked')->default(false);
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('users', function (Blueprint $table) {
29 | $table->dropcolumn('locked');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_222939_following.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->timestamps();
19 | $table->integer('user_id');
20 | $table->integer('profile_id');
21 | $table->boolean('pending')->default(true);
22 | $table->dateTime('confirmed_at')->nullable();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('following');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_230840_pinned_posts.php:
--------------------------------------------------------------------------------
1 | boolean('pinned')->default(false)->index();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('posts', function (Blueprint $table) {
29 | $table->dropcolumn('pinned');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_01_232403_user_images.php:
--------------------------------------------------------------------------------
1 | string('photo', 512)->nullable();
18 | $table->string('banner', 512)->nullable();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::table('users', function (Blueprint $table) {
30 | $table->dropcolumn('photo');
31 | $table->dropcolumn('banner');
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_02_011659_user_bio.php:
--------------------------------------------------------------------------------
1 | text('bio')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('users', function (Blueprint $table) {
29 | $table->dropcolumn('bio');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_02_013254_user_keyvalues.php:
--------------------------------------------------------------------------------
1 | longtext('properties')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('users', function (Blueprint $table) {
29 | $table->dropcolumn('properties');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/seeds/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call(UsersTableSeeder::class);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "npm run development -- --watch",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "axios": "^0.18",
14 | "bootstrap": "^4.0.0",
15 | "popper.js": "^1.12",
16 | "cross-env": "^5.1",
17 | "jquery": "^3.2",
18 | "laravel-mix": "^2.0",
19 | "lodash": "^4.17.4",
20 | "vue": "^2.5.7"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |