Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop update from head fork #3

Merged
merged 58 commits into from
Dec 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
ecc0c25
Dev packages are installed by default now.
cybernet Nov 6, 2015
ec0756c
Create UserController
Nov 7, 2015
33865fd
Create RegisterController
Nov 7, 2015
60149a2
Move register and verify view files to view/register
Nov 8, 2015
41f82c2
Remove register and verify view files from view/login directory
Nov 8, 2015
35d1e12
Move register and user action methods to their new controllers
Nov 8, 2015
68e7b00
Rename view/register/register.php to index.php
Nov 8, 2015
8d6c6b3
Changed View->render and Redirect::to()
Nov 8, 2015
3991e31
Move view user action files to view/user directory
Nov 8, 2015
3683f0a
Fixed logout if CSRF not valid
Nov 8, 2015
314e245
Rename showProfile.php to index.php
Nov 8, 2015
17cca66
Fixed showCaptcha
Nov 8, 2015
c084b99
Changed form actions in user views
Nov 8, 2015
94f8473
Change form action in register views
Nov 8, 2015
fa2cc4c
Fixed links in right menu
Nov 8, 2015
8904ac2
Fixed typo
Nov 9, 2015
1c3faf9
Move Auth::checkAuthentication() to constructor
Nov 9, 2015
90d6f2b
Fixed register link
Nov 9, 2015
2485385
simplifying Session::get($key)
geozak Nov 12, 2015
0150884
Update Session.php to also account for issue 746
geozak Nov 13, 2015
95775a5
added vendor dir to .gitignore
kuldipem Nov 15, 2015
cdde4ef
Fixed major and minor changes missed
Nov 21, 2015
851d594
Improved logout
Nov 21, 2015
9141b70
Revert "Improved logout"
Nov 21, 2015
260ceeb
logo is now centered (to fit GitHub's new and wider UI)
panique Nov 28, 2015
bd14d23
reverted: logo is now centered (to fit GitHub's new and wider UI)
panique Nov 28, 2015
b6955bc
Add encodeHTML() method
OmarElgabry Nov 28, 2015
dbda3f9
Avoid XSS attacks of redirect
OmarElgabry Nov 28, 2015
c310928
Merge pull request #753 from OmarElGabry/master
panique Nov 29, 2015
f036559
Merge pull request #751 from kuldipem/patch-1
panique Nov 29, 2015
72395d2
Merge branch 'master' into develop
panique Nov 29, 2015
da2c9ac
gitignore additions
panique Nov 29, 2015
a388211
Merge pull request #745 from cybernet/patch-1
panique Nov 29, 2015
ab8f273
Merge branch 'master' into develop
panique Nov 29, 2015
3e302f2
removed ""--dev" from composer install line as composer installs dev …
panique Nov 29, 2015
221cb7c
Merge pull request #748 from geozak/patch-1
panique Nov 29, 2015
199ba6a
Merge branch 'master' into develop
panique Nov 29, 2015
d2c076e
Merge pull request #747 from slaveek/login-cotroller
panique Nov 29, 2015
40d8939
Merge remote-tracking branch 'origin/develop' into develop
panique Nov 29, 2015
33a8988
TODO for config encryption stuff
panique Nov 29, 2015
c432766
better info on cookie domains
panique Nov 29, 2015
a5411d4
better info on cookie encryption, code reformattings
panique Nov 29, 2015
4eda9e7
announcing soft EOL for the project
panique Nov 29, 2015
ee76f16
removed weird comment
panique Nov 29, 2015
8293084
added links to session duration to readme
panique Nov 29, 2015
336b43e
removed TODO
panique Nov 29, 2015
3858e8f
added "logged in from multiple devices" info to readme, code comment …
panique Nov 29, 2015
4ddd4a6
added "logged in from multiple devices" info to readme
panique Nov 29, 2015
7ce1ade
Ticket #723 - Confirm email address in registration (to prevent typos…
panique Nov 29, 2015
7b00ea0
Ticket #750 - Auto logout user after acount deleted or suspended
panique Nov 29, 2015
90b72cf
Ticket #538 - throttle password-reset-requests with captcha (to block…
panique Nov 29, 2015
5612ae9
Ticket #721 - feedback notice for: Some mail providers (Yahoo) might …
panique Nov 29, 2015
7bdcad6
badge - codacy
panique Nov 29, 2015
84e5377
Ticket #661 - more security in .htaccess
panique Dec 1, 2015
22878ca
explanation on how to use the CSRF feature
panique Dec 1, 2015
953a985
explanation on how to use the CSRF feature
panique Dec 1, 2015
f914279
fixed broken static call to non-static method
Dec 1, 2015
fdbbb19
fixed broken static call to non-static method in documentation
Dec 1, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
/nbproject/private/
# this is just a demo .gitignore file, put all the files and folders you want to be ignored by git inside
# if you work with NetBeans: ignore NetBeans project files
/nbproject/private/
# don't commit all the dependencies fetched via Composer
/vendor/
7 changes: 6 additions & 1 deletion .htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ RewriteRule ^(.*) public/$1 [L]
</IfModule>

<ifmodule mod_headers.c>
# if you want to prevent your site from being embedded into other sites via an iframe (sometimes used for scam), then
# simply uncomment these lines below. you need to have apache rewrite headers activated, usually via
# "a2enmod rewrite headers" on the command line
#Header set X-Frame-Options Deny
#Header always append X-Frame-Options SAMEORIGIN
<filesmatch "\\.(ico|jpe?g|png|gif|swf)$">
Header set Cache-Control "max-age=2592000, public"
</filesmatch>
Expand All @@ -44,4 +49,4 @@ RewriteRule ^(.*) public/$1 [L]
<filesmatch "\\.(js)$">
Header set Cache-Control "max-age=216000, private"
</filesmatch>
</ifmodule>
</ifmodule>
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ before_script:
- sudo service apache2 restart
# composer
- composer self-update
- composer install --prefer-source --no-interaction --dev
- composer install --prefer-source --no-interaction
# go to tests folder
- cd tests

Expand All @@ -33,4 +33,4 @@ script: phpunit --configuration phpunit.xml --coverage-text --coverage-clover=co
# gets tools from Scrutinizer, uploads unit tests results to Scrutinizer (?)
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
106 changes: 83 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/panique/huge/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/panique/huge/?branch=master)
[![Code Climate](https://codeclimate.com/github/panique/huge/badges/gpa.svg)](https://codeclimate.com/github/panique/huge)
[![Codacy Badge](https://api.codacy.com/project/badge/grade/a0534fce01a74d94a365592edf3c7ad6)](https://www.codacy.com/app/jaimefjorge/php-login)
[![Travis CI](https://travis-ci.org/panique/huge.svg?branch=master)](https://travis-ci.org/panique/huge)
[![Dependency Status](https://www.versioneye.com/user/projects/54ca11fbde7924f81a000010/badge.svg?style=flat)](https://www.versioneye.com/user/projects/54ca11fbde7924f81a000010)

Expand All @@ -25,10 +26,31 @@ Some interesting Buzzwords in this context: [KISS](http://en.wikipedia.org/wiki/
[YAGNI](http://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it), [Feature Creep](https://en.wikipedia.org/wiki/Feature_creep),
[Minimum viable product](https://en.wikipedia.org/wiki/Minimum_viable_product).

#### Current releases
#### HUGE has reached "soft End Of Life"

To keep this project stable, secure, clean and minimal I've decided to reduce the development of HUGE to a
minimum. *Don't worry, this is actually a good thing:* New features usually mean new bugs, lots of testing, fixes,
incompatibilities, and for some people even hardcore update stress. As HUGE is a security-critical script new features
are not as important as a stable and secure core, this is why people use it. This means:

- HUGE will not get new features
- but will be maintained, so it will get bugfixes, corrections etc for sure, maybe for years

And to be honest, maintaining a framework for free in my rare free-time is also not what I want to do permanently. :)

Finally a little note: The PHP world has evolved dramatically, we have excellent frameworks with awesome features and
big professional teams behind, very well written documentations and large communities, so there's simply no reason
to put much work into another framework. Instead, please commit to the popular frameworks, then your work will have
much more impact and is used by much more people!

Thanks to everybody around this project, have a wonderful time!
XOXO,
Chris

#### Releases & development

* stable [v3.1](https://github.com/panique/huge/releases/tag/v3.1),
* public beta branch: [master-branch](https://github.com/panique/huge)
* public beta branch: [master](https://github.com/panique/huge)
* public in-development branch (please commit new code here): [develop](https://github.com/panique/huge/tree/develop)

#### Quick-Index
Expand All @@ -46,9 +68,11 @@ Some interesting Buzzwords in this context: [KISS](http://en.wikipedia.org/wiki/
- [Quick Installation](#quick-installation)
- [Detailed Installation](#detailed-installation)
- [NGINX setup](#nginx-setup)
+ [Documentation](#documentation)
+ [Documentation](#documentation)
- [How to use the user roles](#user_roles)
- [How to use the CSRF feature](#csrf)
+ [Community-provided features & feature discussions](#community)
+ [Potential features for the future (or your forks)](#future)
+ [Future of the project, announcing soft EOL](#future)
+ [Why is there no support forum anymore ?](#why-no-support-forum)
+ [Zero tolerance for idiots, trolls and vandals](#zero-tolerance)
+ [Contribute](#contribute)
Expand Down Expand Up @@ -103,7 +127,7 @@ And why the name "HUGE" ? It's a nice combination to
* fits PSR-0/1/2/4 coding guidelines
* uses [Post-Redirect-Get pattern](https://en.wikipedia.org/wiki/Post/Redirect/Get) for nice application flow
* masses of comments
* is actively developed, maintained and bug-fixed
* is actively maintained and bug-fixed (however, no big new features as project slowly reaches End of Life)

### Planned features

Expand Down Expand Up @@ -346,12 +370,13 @@ There are several files in the root folder of the project that might be irritati

A real documentation is in the making. Until then, please have a look at the code and use your IDE's code completion
features to get an idea how things work, it's quite obvious when you look at the controller files, the model files and
how data is shown in the view files. A big sorry that there's no documentation yet, but time is rare :)
how data is shown in the view files. A big sorry that there's no documentation yet, but time is rare and we are all
doing this for free in our free time :)

- TODO: Full documentation
- TODO: Basic examples on how to do things

#### The different user roles
#### How to use the different user roles <a name="user_roles"></a>

Currently there are two types of users: Normal users and admins. There are exactly the same, but...

Expand All @@ -360,13 +385,17 @@ have a value of `7` inside the database table field `user_account_type`. They ca
(as this wouldn't make sense).

2. Normal users don't have admin features for sure. But they can upgrade and downgrade their accounts (try it out via
/login/changeUserRole), which is basically a super-simple implementation of the basic-user / premium-user concept.
/user/changeUserRole), which is basically a super-simple implementation of the basic-user / premium-user concept.
Normal users have a value of `1` or `2` inside the database table field `user_account_type`. By default all new
registered users are normal users with user role 1 for sure.

See the "Testing with demo users" section of this readme for more info.

#### An introduction into the CSRF features
There's also a very interesting [pull request adding user roles and user permissions](https://github.com/panique/huge/pull/691),
which is not integrated into the project as it's too advanced and complex. But, this might be exactly what you need,
feel free to try.

#### How to use the CSRF feature <a name="csrf"></a>

To prevent [CSRF attacks](https://en.wikipedia.org/wiki/Cross-site_request_forgery), HUGE does this in the most common
way, by using a security *token* when the user submits critical forms. This means: When PHP renders a form for the user,
Expand All @@ -375,11 +404,28 @@ the application puts a "random string" inside the form (as a hidden input field)
checks if the POST request contains exactly the form token that is inside the session.

This CSRF prevention feature is currently implemented on the login form process (see *application/view/login/index.php*)
and user name change form process (see *application/view/login/editUsername.php*), most other forms are not security-
and user name change form process (see *application/view/user/editUsername.php*), most other forms are not security-
critical and should stay as simple as possible.

So, to do this with a normal form, simply: At your form, before the submit button put:
`<input type="hidden" name="csrf_token" value="<?= Csrf::makeToken(); ?>" />`
Then, in the controller action validate the CSRF token submitted with the form by doing:
```
// check if csrf token is valid
if (!Csrf::isTokenValid()) {
LoginModel::logout();
Redirect::home();
exit();
}
```

A big thanks to OmarElGabry for implementing this!

#### Can a user be logged in from multiple devices ?

In theory: Yes, but this feature didn't work in my tests. As it's an external feature please have a look into the
[according ticket](https://github.com/panique/huge/pull/693) for more.

#### Troubleshooting & Glitches

* In 3.0 and 3.1 a user could log into the application from different devices / browsers / locations. This was intended
Expand All @@ -402,25 +448,37 @@ to go into the main version of HUGE, but have a look into these tickets if you a
- [Internationalization feature](https://github.com/panique/huge/issues/582)
- [Using controller A inside controller B](https://github.com/panique/huge/issues/706)
- [HTML mails](https://github.com/panique/huge/issues/738)
- [Deep user roles / user permission system](https://github.com/panique/huge/pull/691)

### Future of the project: As simple as possible! <a name="future"></a>
### Future of HUGE: Announcing "soft End Of Life" <a name="future"></a>

The idea of this project was to provide a super-simple barebone application with a full user authentication
system inside. For future development it might be cool to avoid feature hell and overbloated code, so please let's keep
this project simple, clean and minimal with these few "rules" (and more on this inside this ticket:
[Keep the project as simple as possible](https://github.com/panique/huge/issues/664).):
The idea of this project is and was to provide a super-simple barebone application with a full user authentication
system inside that just works fine and stable. Due to the highly security-related nature of this script any changes
mean a lot of work, lots of testing, catching edge cases etc., and in the end I spent 90% of the time testing and fixing
new features or new features break existing stuff, and doing this is really not what anybody wants to do for free in
the rare free-time :)

To keep the project stable, clean and maintainable, I would kindly announce the "soft-End of Life" for this project,
meaning:

A. HUGE will not get any new features in the future, but ...
B. bugfixes and corrections will be made, probably for years

### Coding guideline behind HUGE

While HUGE was in development, there were 3 main rules that helped me (and probably others) to write minimal, clean
and working code. Might be useful for you too:

1. Reduce features to the bare minimum.
2. Don't implement features that are not needed by most users.
3. Only build everything for the most common use case (like MySQL, not PostGre, NoSQL etc).

#### List of feature ideas

Open-source is a great thing, and projects live from community-contributed feature for sure. As this project is highly
security-related and mainly just a free-time one-man show, new features mean a lot of work, reviewing, testing,
corrections, and making sure it runs perfectly in every possible scenario. As I simply don't have the time to do this,
I would kindly ask you **not** to commit new features when they are not really basic, very small and well-written,
and if you miss a feature, then please try to write this on your own and commit it to the project.
As noted in the intro of this README, there are also some powerful concepts that might help you when developing cool
stuff: [KISS](http://en.wikipedia.org/wiki/KISS_principle),
[YAGNI](http://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it), [Feature Creep](https://en.wikipedia.org/wiki/Feature_creep),
[Minimum viable product](https://en.wikipedia.org/wiki/Minimum_viable_product).

#### List of features / ideas provided in tickets / pull requests

To avoid unnecessary work for all of us I would kindly recommend everybody to use HUGE for simple project that only
need the features that already exist, and if you really need a RESTful architecture, migrations, routing, 2FA etc,
Expand Down Expand Up @@ -499,7 +557,7 @@ an normal GitHub issue.

### Current and further development

See active issues and requested features here:
See active issues here:
https://github.com/panique/huge/issues?state=open

### Why you should use a favicon.ico in your project :)
Expand All @@ -518,6 +576,8 @@ More here on Stackflow: [How to prevent favicon.ico requests?](http://stackoverf

### Useful links

- [How long will my session last?](http://stackoverflow.com/questions/1516266/how-long-will-my-session-last/1516338#1516338)
- [How to do expire a PHP session after X minutes?](http://stackoverflow.com/questions/520237/how-do-i-expire-a-php-session-after-30-minutes/1270960#1270960)
- [How to use PDO](http://wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers)
- [A short guideline on how to use the PHP 5.5 password hashing functions and its PHP 5.3 & 5.4 implementations](http://www.dev-metal.com/use-php-5-5-password-hashing-functions/)
- [How to setup latest version of PHP 5.5 on Ubuntu 12.04 LTS](http://www.dev-metal.com/how-to-setup-latest-version-of-php-5-5-on-ubuntu-12-04-lts/)
Expand Down
2 changes: 1 addition & 1 deletion _one-click-installation/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ mv composer.phar /usr/local/bin/composer

# go to project folder, load Composer packages
cd "/var/www/html/${PROJECTFOLDER}"
composer install --dev
composer install

# run SQL statements from install folder
sudo mysql -h "localhost" -u "root" "-p${PASSWORD}" < "/var/www/html/${PROJECTFOLDER}/application/_installation/01-create-database.sql"
Expand Down
18 changes: 10 additions & 8 deletions application/config/config.development.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
* won't be accessible by scripting languages, such as JavaScript. This setting can effectively help to reduce identity
* theft through XSS attacks (although it is not supported by all browsers).
*
* IMPORTANT: This will
*
* @see php.net/manual/en/session.configuration.php#ini.session.cookie-httponly
*/
ini_set('session.cookie_httponly', 1);
Expand Down Expand Up @@ -84,10 +82,13 @@
* @see http://stackoverflow.com/q/9618217/1114320
* @see php.net/manual/en/function.setcookie.php
*
* COOKIE_DOMAIN: The domain where the cookie is valid for.
* COOKIE_DOMAIN mightn't work with "localhost", ".localhost", "127.0.0.1", or ".127.0.0.1". If so, leave it as empty string, false or null.
* @see http://stackoverflow.com/questions/1134290/cookies-on-localhost-with-explicit-domain
* @see http://php.net/manual/en/function.setcookie.php#73107
* COOKIE_DOMAIN: The domain where the cookie is valid for. Usually this does not work with "localhost",
* ".localhost", "127.0.0.1", or ".127.0.0.1". If so, leave it as empty string, false or null.
* When using real domains make sure you have a dot (!) in front of the domain, like ".mydomain.com". This is
* strange, but explained here:
* @see http://stackoverflow.com/questions/2285010/php-setcookie-domain
* @see http://stackoverflow.com/questions/1134290/cookies-on-localhost-with-explicit-domain
* @see http://php.net/manual/en/function.setcookie.php#73107
*
* COOKIE_SECURE: If the cookie will be transferred through secured connection(SSL). It's highly recommended to set it to true if you have secured connection.
* COOKIE_HTTP: If set to true, Cookies that can't be accessed by JS - Highly recommended!
Expand All @@ -114,7 +115,8 @@
'AVATAR_DEFAULT_IMAGE' => 'default.jpg',
/**
* Configuration for: Encryption Keys
*
* ENCRYPTION_KEY, HMAC_SALT: Currently used to encrypt and decrypt publicly visible values, like the user id in
* the cookie. Change these values for increased security, but don't touch if you have no idea what this means.
*/
'ENCRYPTION_KEY' => '6#x0gÊìf^25cL1f$08&',
'HMAC_SALT' => '8qk9c^4L6d#15tM8z7n0%',
Expand Down Expand Up @@ -146,7 +148,7 @@
'EMAIL_PASSWORD_RESET_FROM_NAME' => 'My Project',
'EMAIL_PASSWORD_RESET_SUBJECT' => 'Password reset for PROJECT XY',
'EMAIL_PASSWORD_RESET_CONTENT' => 'Please click on this link to reset your password: ',
'EMAIL_VERIFICATION_URL' => 'login/verify',
'EMAIL_VERIFICATION_URL' => 'register/verify',
'EMAIL_VERIFICATION_FROM_EMAIL' => 'no-reply@example.com',
'EMAIL_VERIFICATION_FROM_NAME' => 'My Project',
'EMAIL_VERIFICATION_SUBJECT' => 'Account activation for PROJECT XY',
Expand Down
4 changes: 3 additions & 1 deletion application/config/texts.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"FEEDBACK_DELETED" => "Your account has been deleted.",
"FEEDBACK_ACCOUNT_SUSPENDED" => "Account Suspended for ",
"FEEDBACK_ACCOUNT_SUSPENSION_DELETION_STATUS" => "This user's suspension / deletion status has been edited.",
"FEEDBACK_ACCOUNT_USER_SUCCESSFULLY_KICKED" => "The selected user has been successfully kicked out of the system (by resetting this user's session)",
"FEEDBACK_PASSWORD_WRONG_3_TIMES" => "You have typed in a wrong password 3 or more times already. Please wait 30 seconds to try again.",
"FEEDBACK_ACCOUNT_NOT_ACTIVATED_YET" => "Your account is not activated yet. Please click on the confirm link in the mail.",
"FEEDBACK_USERNAME_OR_PASSWORD_WRONG" => "The username or password is incorrect. Please try again.",
Expand All @@ -21,6 +22,7 @@
"FEEDBACK_USERNAME_OR_PASSWORD_FIELD_EMPTY" => "Username or password field was empty.",
"FEEDBACK_USERNAME_EMAIL_FIELD_EMPTY" => "Username / email field was empty.",
"FEEDBACK_EMAIL_FIELD_EMPTY" => "Email field was empty.",
"FEEDBACK_EMAIL_REPEAT_WRONG" => "Email and email repeat are not the same",
"FEEDBACK_EMAIL_AND_PASSWORD_FIELDS_EMPTY" => "Email and password fields were empty.",
"FEEDBACK_USERNAME_SAME_AS_OLD_ONE" => "Sorry, that username is the same as your current one. Please choose another one.",
"FEEDBACK_USERNAME_ALREADY_TAKEN" => "Sorry, that username is already taken. Please choose another one.",
Expand All @@ -41,7 +43,7 @@
"FEEDBACK_VERIFICATION_MAIL_SENDING_ERROR" => "Verification mail could not be sent due to: ",
"FEEDBACK_VERIFICATION_MAIL_SENDING_SUCCESSFUL" => "A verification mail has been sent successfully.",
"FEEDBACK_ACCOUNT_ACTIVATION_SUCCESSFUL" => "Activation was successful! You can now log in.",
"FEEDBACK_ACCOUNT_ACTIVATION_FAILED" => "Sorry, no such id/verification code combination here...",
"FEEDBACK_ACCOUNT_ACTIVATION_FAILED" => "Sorry, no such id/verification code combination here! It might be possible that your mail provider (Yahoo? Hotmail?) automatically visits links in emails for anti-scam scanning, so this activation link might been clicked without your action. Please try to log in on the main page.",
"FEEDBACK_AVATAR_UPLOAD_SUCCESSFUL" => "Avatar upload was successful.",
"FEEDBACK_AVATAR_UPLOAD_WRONG_TYPE" => "Only JPEG and PNG files are supported.",
"FEEDBACK_AVATAR_UPLOAD_TOO_SMALL" => "Avatar source file's width/height is too small. Needs to be 100x100 pixel minimum.",
Expand Down
Loading