├── .gitignore ├── .htaccess ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── Vagrantfile ├── action.php ├── assets ├── bootstrap-3-customized.json ├── css │ ├── application.css │ ├── application.css.map │ ├── application.scss │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ ├── material-icons.css │ └── material-icons.min.css ├── fonts │ ├── MaterialIcons-Regular.eot │ ├── MaterialIcons-Regular.ijmap │ ├── MaterialIcons-Regular.svg │ ├── MaterialIcons-Regular.ttf │ ├── MaterialIcons-Regular.woff │ └── MaterialIcons-Regular.woff2 ├── img │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ └── safari-pinned-tab.svg ├── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ └── passy.js └── src │ ├── js │ └── passy.js │ └── scss │ ├── _animations.scss │ ├── _base.scss │ ├── _shadows.scss │ ├── _structure.scss │ ├── application.scss │ └── components │ ├── _all.scss │ ├── _buttons.scss │ ├── _dropdown.scss │ ├── _inputs.scss │ ├── _misc.scss │ ├── _modals.scss │ ├── _navbar.scss │ └── _snackbar.scss ├── browserconfig.xml ├── composer.json ├── composer.lock ├── config.inc.php ├── examples └── nginx_site.conf ├── gulpfile.js ├── index.php ├── manifest.json ├── meta.inc.php ├── package.json ├── page ├── page_archived_password_list.inc.php ├── page_login.inc.php ├── page_login_history.inc.php ├── page_password_list.inc.php ├── page_register.inc.php └── page_user_settings.inc.php ├── src ├── .htaccess ├── PASSY │ ├── Database.php │ ├── Format.php │ ├── IPLog.php │ ├── PASSY.php │ ├── Passwords.php │ ├── Response.php │ ├── Tasks.php │ ├── TwoFactor.php │ ├── UserManager.php │ ├── Util.php │ └── Validate.php └── autoload.php └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .sass-cache/ 3 | .vagrant/ 4 | vendor/ 5 | node_modules/ 6 | *.log 7 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | deny from all 3 | 4 | 5 | 6 | allow from all 7 | 8 | 9 | 10 | allow from all 11 | 12 | 13 | Options -Indexes 14 | order deny,allow 15 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | Last change: 21 July 2017 74 | 75 | [homepage]: http://contributor-covenant.org 76 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue before making a change. 4 | 5 | Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project. 6 | 7 | ## Development Workflow 8 | We use [Yarn](https://yarnpkg.com) to manage dependencies for frontend as well as development. 9 | Additionally we use [Composer](https://getcomposer.org/) to manage backend dependencies (PHP). 10 | We use SCSS as a CSS preprocessor and Babel as a JavaScript preprocessor. 11 | 12 | ## Pull Request Process 13 | 14 | 1. Ensure, that no IDE / editor related files were pushed. ([How to use .gitignore](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository#_ignoring)) 15 | 2. Update the README.md with details of changes to dependencies. (Like required php extensions or config changes) 16 | 3. You may merge the Pull Request in once you have the sign-off of a developer. 17 | 18 | ## Bug reporting 19 | 20 | 1. Try to reproduce the issue, you experienced. 21 | 2. If you have managed to reproduce ths issue, start explaining, how the issue can be reproduced. If you could not reproduce the issue, provide as many information you can. 22 | 3. Provide version, that is in use and any modifications made to the project. 23 | 4. Your issue may be reviewed by a maintainer and be solved. 24 | 25 | ## Feature ideas 26 | 27 | 1. Create an issue, where you exactly explain your idea. 28 | 2. Add an example or reference to an other project's feature. 29 | 3. Your idea may be reviewed by a maintainer and be accepted or closed. 30 | 31 | ## Tips 32 | To setup a suitable test environment you can use the predefined `Vagrantfile` in combination with [Vagrant](https://www.vagrantup.com/). 33 | Execute the command `vagrant up` or use the frontend provided by your IDE, after [installing Vagrant](https://www.vagrantup.com/downloads.html). 34 | 35 | 36 | Last change: 21 July 2017 37 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | PASSY - Modern HTML5 Password Manager 2 | Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project has moved to GitLab.com 2 | https://gitlab.com/PASSYpw 3 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | # https://docs.vagrantup.com. 3 | 4 | config.vm.box = "debian/stretch64" 5 | config.vm.box_check_update = true 6 | 7 | config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'" 8 | 9 | config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "0.0.0.0" 10 | config.vm.network "forwarded_port", guest: 3306, host: 3366 11 | 12 | config.vm.provider "virtualbox" do |vb| 13 | vb.memory = "2048" 14 | end 15 | 16 | config.vm.provision "shell", inline: <<-SHELL 17 | export DEBIAN_FRONTEND=noninteractive 18 | apt-get update 19 | apt-get autoremove -y --purge apache2 20 | apt-get upgrade -y 21 | apt-get install -y nginx php7.0-fpm php7.0-json php7.0-curl php7.0-mysql mariadb-server 22 | ln -s /vagrant /var/www/passy 23 | cp /vagrant/examples/nginx_site.conf /etc/nginx/sites-enabled/default 24 | service nginx restart 25 | mysql -uroot -e "CREATE DATABASE IF NOT EXISTS passy;" 26 | mysql -uroot -e "CREATE USER 'passy'@'%' IDENTIFIED BY '';" 27 | mysql -uroot -e "GRANT USAGE ON *.* TO 'passy'@'%' IDENTIFIED BY ''" 28 | mysql -uroot -e "GRANT ALL PRIVILEGES ON passy.* TO 'passy'@'%';" 29 | mysql -uroot -e "FLUSH PRIVILEGES;" 30 | sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/my.cnf 31 | service mysql restart 32 | SHELL 33 | end 34 | -------------------------------------------------------------------------------- /action.php: -------------------------------------------------------------------------------- 1 | (https://scrumplex.net) 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | require_once __DIR__ . "/src/autoload.php"; 22 | require_once __DIR__ . "/vendor/autoload.php"; 23 | require_once __DIR__ . "/config.inc.php"; 24 | require_once __DIR__ . "/meta.inc.php"; 25 | 26 | use PASSY\Database; 27 | use PASSY\Tasks; 28 | use PASSY\TwoFactor; 29 | use PASSY\UserManager; 30 | use PASSY\Passwords; 31 | use PASSY\IPLog; 32 | use PASSY\Response; 33 | use PASSY\Util; 34 | use PASSY\Validate; 35 | 36 | $unauthenticatedActions = array( 37 | "user/login" => true, 38 | "user/logout" => true, 39 | "user/register" => $generalConfig["registration"]["enabled"], 40 | "status" => true 41 | ); 42 | 43 | $authenticatedActions = array( 44 | "password/create" => true, 45 | "password/edit" => true, 46 | "password/query" => true, 47 | "password/queryAll" => true, 48 | "password/archive" => true, 49 | "password/restore" => true, 50 | "password/delete" => true, 51 | "iplog/queryAll" => $generalConfig["login_history"]["enabled"], 52 | "user/changeUsername" => true, 53 | "user/changePassword" => true, 54 | "user/2faGenerateKey" => true, 55 | "user/2faEnable" => true, 56 | "user/2faDisable" => true, 57 | "misc/export" => true, 58 | "misc/import" => true 59 | ); 60 | 61 | $db = new Database($mysqlConfig); 62 | $ipLog = new IPLog(); 63 | $passwords = new Passwords(); 64 | $tasks = new Tasks(); 65 | $twoFactor = new TwoFactor(); 66 | $userManager = new UserManager($generalConfig["redirect_ssl"]); 67 | 68 | $action = @$_POST["a"]; 69 | 70 | header("Content-Type: application/json; charset=UTF-8"); 71 | 72 | // Report exceptions to error log and print error message 73 | set_exception_handler(function ($e) { 74 | Util::handleException($e); 75 | }); 76 | 77 | $userManager->checkSessionExpiration(); 78 | 79 | if (array_key_exists($action, $unauthenticatedActions) && $unauthenticatedActions[$action]) { 80 | switch ($action) { 81 | case "user/login": 82 | $username = $_POST["username"]; 83 | $password = $_POST["password"]; 84 | 85 | if (!Validate::validateLoginUsername($username)) { 86 | $response = new Response(false, "invalid_username"); 87 | die($response->getJSONResponse()); 88 | } 89 | if (!Validate::validateLoginPassword($password)) { 90 | $response = new Response(false, "invalid_password"); 91 | die($response->getJSONResponse()); 92 | } 93 | 94 | $persistent = isset($_POST["persistent"]) && $_POST["persistent"] == "on"; 95 | 96 | $result = $userManager->_login($username, $password); 97 | if ($result->wasSuccess()) { 98 | if ($twoFactor->isEnabled($_SESSION["userId"])) { 99 | if (isset($_POST["2faCode"])) { 100 | $twoFactorCode = $_POST["2faCode"]; 101 | $twoFactorCode = trim($twoFactorCode); 102 | if (strlen($twoFactorCode) == 6) { 103 | $result = $twoFactor->_checkCode($_SESSION["userId"], $password, $twoFactorCode); 104 | if ($result->wasSuccess()) { 105 | $ipLog->_logIP($_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"], $userManager->getUserID()); 106 | if ($persistent) 107 | $userManager->setSessionExpirationTime(0); 108 | die($result->getJSONResponse()); 109 | } 110 | } else if (strlen($twoFactorCode) == TwoFactor::$KEYLENGTH) { 111 | if ($twoFactor->checkPrivateKey($_SESSION["userId"], $password, $twoFactorCode)) { 112 | $twoFactor->_disable2FA($_SESSION["userId"]); 113 | $ipLog->_logIP($_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"], $userManager->getUserID()); 114 | if ($persistent) 115 | $userManager->setSessionExpirationTime(0); 116 | $response = new Response(true, array()); 117 | die($response->getJSONResponse()); 118 | } 119 | } else { 120 | $result = new Response(false, "invalid_code"); 121 | } 122 | $userManager->_logout(); 123 | die($result->getJSONResponse()); 124 | } 125 | $userManager->_logout(); 126 | $response = new Response(false, "two_factor_needed"); 127 | die($response->getJSONResponse()); 128 | } 129 | 130 | $ipLog->_logIP($_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"], $userManager->getUserID()); 131 | if ($persistent) 132 | $userManager->setSessionExpirationTime(0); 133 | } 134 | 135 | die($result->getJSONResponse()); 136 | break; 137 | 138 | case "user/logout": 139 | $result = $userManager->_logout(); 140 | die($result->getJSONResponse()); 141 | break; 142 | 143 | case "user/register": 144 | $username = $_POST["username"]; 145 | $password = $_POST["password"]; 146 | $password2 = $_POST["password2"]; 147 | 148 | if (!Validate::validateLoginUsername($username)) { 149 | $response = new Response(false, "invalid_username"); 150 | die($response->getJSONResponse()); 151 | } 152 | 153 | if ($generalConfig["recaptcha"]["enabled"]) { 154 | $recaptcha = new \ReCaptcha\ReCaptcha($generalConfig["recaptcha"]["private_key"]); 155 | $resp = $recaptcha->verify($_POST["g-recaptcha-response"], $_SERVER["REMOTE_ADDR"]); 156 | if (!$resp->isSuccess()) { 157 | $response = new Response(false, "recaptcha_fail"); 158 | die($response->getJSONResponse()); 159 | } 160 | } 161 | 162 | if ($password != $password2) { 163 | $response = new Response(false, "passwords_not_matching"); 164 | die($response->getJSONResponse()); 165 | } 166 | 167 | if (!Validate::validateLoginPassword($password)) { 168 | $response = new Response(false, "invalid_password"); 169 | die($response->getJSONResponse()); 170 | } 171 | 172 | $result = $userManager->_register($username, $password); 173 | 174 | die($result->getJSONResponse()); 175 | break; 176 | 177 | case "status": 178 | die($userManager->_status()->getJSONResponse()); 179 | break; 180 | } 181 | } else if (array_key_exists($action, $authenticatedActions) && $authenticatedActions[$action]) { 182 | try { 183 | 184 | if ($userManager->isAuthenticated()) { 185 | $userManager->trackActivity(); 186 | switch ($action) { 187 | case "password/create": 188 | $username = $_POST["username"]; 189 | $password = $_POST["password"]; 190 | $description = $_POST["description"]; 191 | $result = $passwords->_create($username, $password, $description, $userManager->getUserID(), $userManager->getMasterPassword()); 192 | die($result->getJSONResponse()); 193 | break; 194 | case "password/edit": 195 | $passwordId = $_POST["id"]; 196 | $username = $_POST["username"]; 197 | $password = $_POST["password"]; 198 | $description = $_POST["description"]; 199 | $result = $passwords->_edit($passwordId, $username, $password, $description, $userManager->getMasterPassword()); 200 | die($result->getJSONResponse()); 201 | break; 202 | 203 | case "misc/import": 204 | $withPassword = isset($_POST["with-pass"]) && $_POST["with-pass"] == "on"; 205 | $exportPassword = $userManager->getMasterPassword(); 206 | if (isset($_POST["pass"]) && strlen($_POST["pass"]) != 0) $exportPassword = $_POST["pass"]; 207 | $content = $_FILES['parse-file']['tmp_name']; 208 | if (Util::endsWith($_FILES['parse-file']['name'], ".passy-json")) { 209 | $result = $passwords->_import(file_get_contents($content), $userManager->getUserID(), $userManager->getMasterPassword(), $withPassword, $exportPassword); 210 | die($result->getJSONResponse()); 211 | } elseif (Util::endsWith($_FILES['parse-file']['name'], ".csv")) { 212 | $result = $passwords->_import(file_get_contents($content), $userManager->getUserID(), $userManager->getMasterPassword(), $withPassword, $exportPassword, "CSV"); 213 | die($result->getJSONResponse()); 214 | } else { 215 | $result = new Response(false, "not_supported_format"); 216 | die($result->getJSONResponse()); 217 | } 218 | 219 | break; 220 | 221 | case "misc/export": 222 | $withPassword = isset($_POST["with-pass"]) && $_POST["with-pass"] == "on"; 223 | $exportPassword = $userManager->getMasterPassword(); 224 | if (isset($_POST["pass"]) && strlen($_POST["pass"]) != 0) $exportPassword = $_POST["pass"]; 225 | $result = $passwords->_exportAll($userManager->getUserID(), $userManager->getMasterPassword(), $withPassword, $exportPassword); 226 | $json = $result->getJSONResponse(); 227 | header('Content-Description: File Transfer'); 228 | header('Content-Type: application/octet-stream'); 229 | header('Content-Disposition: attachment; filename=PASSY-Export-' . time() . ".passy-json"); 230 | header('Expires: 0'); 231 | header('Cache-Control: must-revalidate'); 232 | header('Pragma: public'); 233 | header('Content-Length: ' . strlen($json)); 234 | die($json); 235 | break; 236 | case "password/query": 237 | $passwordId = $_POST["id"]; 238 | $result = $passwords->_query($passwordId, $userManager->getMasterPassword()); 239 | die($result->getJSONResponse()); 240 | break; 241 | 242 | case "password/queryAll": 243 | $result = $passwords->_queryAll($userManager->getUserID()); 244 | die($result->getJSONResponse()); 245 | break; 246 | 247 | case "password/archive": 248 | $passwordId = $_POST["id"]; 249 | $result = $passwords->_archive($passwordId); 250 | die($result->getJSONResponse()); 251 | break; 252 | 253 | case "password/restore": 254 | $passwordId = $_POST["id"]; 255 | $result = $passwords->_restore($passwordId); 256 | die($result->getJSONResponse()); 257 | break; 258 | 259 | case "password/delete": 260 | $passwordId = $_POST["id"]; 261 | $result = $passwords->_delete($passwordId); 262 | die($result->getJSONResponse()); 263 | break; 264 | 265 | case "iplog/queryAll": 266 | $result = $ipLog->_queryAll($userManager->getUserID()); 267 | die($result->getJSONResponse()); 268 | break; 269 | 270 | case "user/changeUsername": 271 | $password = $_POST["password"]; 272 | $newUsername = $_POST["new_username"]; 273 | if (!$userManager->checkPassword($password)) { 274 | $response = new Response(false, "invalid_credentials"); 275 | die($response->getJSONResponse()); 276 | } 277 | $result = $userManager->_changeUsername($userManager->getUserID(), $newUsername); 278 | die($result->getJSONResponse()); 279 | break; 280 | 281 | case "user/changePassword": 282 | $password = $_POST["password"]; 283 | $newPassword = $_POST["new_password"]; 284 | $newPassword2 = $_POST["new_password2"]; 285 | if (!$userManager->checkPassword($password)) { 286 | $response = new Response(false, "invalid_credentials"); 287 | die($response->getJSONResponse()); 288 | } 289 | if ($newPassword != $newPassword2) { 290 | $response = new Response(false, "passwords_not_matching"); 291 | die($response->getJSONResponse()); 292 | } 293 | 294 | $result = $userManager->_changePassword($userManager->getUserID(), $userManager->getMasterPassword(), $newPassword); 295 | die($result->getJSONResponse()); 296 | break; 297 | 298 | case "user/2faGenerateKey": 299 | if ($twoFactor->isEnabled($_SESSION["userId"])) { 300 | $response = new Response(false, "2fa_enabled"); 301 | die($response->getJSONResponse()); 302 | } 303 | die($twoFactor->_generateSecretKey($_SESSION["username"])->getJSONResponse()); 304 | break; 305 | 306 | case "user/2faEnable": 307 | if ($twoFactor->isEnabled($_SESSION["userId"])) { 308 | $response = new Response(false, "2fa_enabled"); 309 | die($response->getJSONResponse()); 310 | } 311 | $privateKey = $_POST["2faPrivateKey"]; 312 | $code = $_POST["2faCode"]; 313 | $result = $twoFactor->_enable2FA($_SESSION["userId"], $_SESSION["master_password"], $privateKey, $code); 314 | die($result->getJSONResponse()); 315 | break; 316 | 317 | case "user/2faDisable": 318 | if ($twoFactor->isEnabled($_SESSION["userId"])) { 319 | $twoFactorCode = $_POST["2faCode"]; 320 | $twoFactorCode = trim($twoFactorCode); 321 | if (strlen($twoFactorCode) == 6) { 322 | $result = $twoFactor->_checkCode($_SESSION["userId"], $_SESSION["master_password"], $twoFactorCode); 323 | if ($result->wasSuccess()) { 324 | $result = $twoFactor->_disable2FA($_SESSION["userId"]); 325 | die($result->getJSONResponse()); 326 | } 327 | } else if (strlen($twoFactorCode) == TwoFactor::$KEYLENGTH) { 328 | if ($twoFactor->checkPrivateKey($_SESSION["userId"], $_SESSION["master_password"], $twoFactorCode)) { 329 | $result = $twoFactor->_disable2FA($_SESSION["userId"]); 330 | die($result->getJSONResponse()); 331 | } 332 | } 333 | $result = new Response(false, "invalid_code"); 334 | die($result->getJSONResponse()); 335 | } 336 | $response = new Response(true, array()); 337 | die($response->getJSONResponse()); 338 | break; 339 | } 340 | } else { 341 | $response = new Response(false, "not_authenticated"); 342 | die($response->getJSONResponse()); 343 | } 344 | } catch (Exception $e) { 345 | Util::handleException($e); 346 | } 347 | } 348 | 349 | $response = new Response(false, "invalid_request"); 350 | die($response->getJSONResponse()); 351 | -------------------------------------------------------------------------------- /assets/css/application.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "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", 4 | "sources": ["../scss/application.scss","../scss/_base.scss","../scss/_shadows.scss","../scss/_structure.scss","../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/css3/_opacity.scss","../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/_support.scss","../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/css3/_transition.scss","../scss/_animations.scss","../scss/components/_buttons.scss","../scss/components/_navbar.scss","../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/css3/_animation.scss","../scss/components/_inputs.scss","../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/css3/_transform.scss","../scss/components/_modals.scss","../scss/components/_snackbar.scss","../scss/components/_misc.scss"], 5 | "names": [], 6 | "file": "application.css" 7 | } -------------------------------------------------------------------------------- /assets/css/application.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * PASSY 2.x.x 3 | * Copyright 2017 Sefa Eyeoglu 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | @import "../../node_modules/awesome-sass-easing/sass-easing";@import "base";@import "shadows";@import "structure";@import "animations";@import "components/all"; -------------------------------------------------------------------------------- /assets/css/material-icons.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @font-face { 17 | font-family: 'Material Icons'; 18 | font-style: normal; 19 | font-weight: 400; 20 | src: url(../fonts/MaterialIcons-Regular.eot); 21 | src: local('Material Icons'), local('MaterialIcons-Regular'), url(../fonts/MaterialIcons-Regular.woff2) format('woff2'), url(../fonts/MaterialIcons-Regular.woff) format('woff'), url(../fonts/MaterialIcons-Regular.ttf) format('truetype') 22 | } 23 | 24 | .material-icons { 25 | font-family: 'Material Icons'; 26 | font-weight: 400; 27 | font-style: normal; 28 | display: inline-block; 29 | line-height: 1; 30 | text-transform: none; 31 | letter-spacing: normal; 32 | word-wrap: normal; 33 | white-space: nowrap; 34 | direction: ltr; 35 | vertical-align: middle; 36 | -webkit-font-smoothing: antialiased; 37 | text-rendering: optimizeLegibility; 38 | -moz-osx-font-smoothing: grayscale; 39 | font-feature-settings: 'liga' 40 | } -------------------------------------------------------------------------------- /assets/css/material-icons.min.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(../fonts/MaterialIcons-Regular.eot);src:local('Material Icons'),local('MaterialIcons-Regular'),url(../fonts/MaterialIcons-Regular.woff2) format('woff2'),url(../fonts/MaterialIcons-Regular.woff) format('woff'),url(../fonts/MaterialIcons-Regular.ttf) format('truetype')}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;vertical-align:middle;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'} -------------------------------------------------------------------------------- /assets/fonts/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/fonts/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /assets/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/fonts/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /assets/fonts/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/fonts/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /assets/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/android-chrome-192x192.png -------------------------------------------------------------------------------- /assets/img/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/android-chrome-512x512.png -------------------------------------------------------------------------------- /assets/img/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /assets/img/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /assets/img/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /assets/img/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /assets/img/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /assets/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/favicon-16x16.png -------------------------------------------------------------------------------- /assets/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/favicon-32x32.png -------------------------------------------------------------------------------- /assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/favicon.ico -------------------------------------------------------------------------------- /assets/img/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/mstile-144x144.png -------------------------------------------------------------------------------- /assets/img/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/mstile-150x150.png -------------------------------------------------------------------------------- /assets/img/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/mstile-310x310.png -------------------------------------------------------------------------------- /assets/img/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PASSYpw/PASSY/d93ff06f91799f93d0c6d17dc841638cd9b1a5fb/assets/img/mstile-70x70.png -------------------------------------------------------------------------------- /assets/img/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/src/scss/_animations.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | @keyframes dropdownIn { 21 | 0% { 22 | transform: translate(50%, -50%) scale(0); 23 | opacity: 0; 24 | } 25 | 26 | 100% { 27 | transform: translate(0, 0) scale(1); 28 | opacity: 1; 29 | } 30 | } 31 | 32 | @keyframes dropdownOut { 33 | 0% { 34 | opacity: 1; 35 | } 36 | 37 | 100% { 38 | opacity: 0; 39 | } 40 | } 41 | 42 | @keyframes dropdownItemsIn { 43 | 0% { 44 | transform: translateY(-50%); 45 | opacity: 0; 46 | } 47 | 48 | 100% { 49 | transform: translateY(0); 50 | opacity: 1; 51 | } 52 | } 53 | 54 | @keyframes contextMenuIn { 55 | 0% { 56 | transform: translate(-50%, -50%) scale(0); 57 | opacity: 0; 58 | } 59 | 60 | 100% { 61 | transform: translate(0, 0) scale(1); 62 | opacity: 1; 63 | } 64 | } 65 | 66 | @keyframes spinnerRotate { 67 | 0% { 68 | transform: rotate(0deg); 69 | } 70 | 100% { 71 | transform: rotate(270deg); 72 | } 73 | } 74 | 75 | @keyframes spinnerDash { 76 | 0% { 77 | stroke-dashoffset: $offsetSpinnerStroke; 78 | } 79 | 50% { 80 | stroke-dashoffset: $offsetSpinnerStroke / 4; 81 | transform: rotate(135deg); 82 | } 83 | 100% { 84 | stroke-dashoffset: $offsetSpinnerStroke; 85 | transform: rotate(450deg); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /assets/src/scss/_base.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | // Theme 21 | $colorBackground: #EEEEEE; 22 | $colorForeground: #FFFFFF; 23 | $colorText: #000000; 24 | $colorSecondaryText: #757575; 25 | $colorDivider: rgba(0, 0, 0, 0.06); 26 | $colorTheme: #FF5722; 27 | $colorThemeText: #FFFFFF; 28 | $colorAccent: #00BCD4; 29 | $colorAccentText: #FFFFFF; 30 | 31 | // Colors 32 | $colorDefault: #ffffff; 33 | $colorPrimary: $colorAccent; 34 | $colorSuccess: #4CAF50; 35 | $colorInfo: #00BCD4; 36 | $colorWarning: #FFC107; 37 | $colorDanger: #f44336; 38 | 39 | // Spinner 40 | $offsetSpinnerStroke: 187; 41 | $durationSpinner: 1.6s; 42 | 43 | // Values 44 | $scrollbarWidth: 15px; 45 | 46 | // Font 47 | @import url(https://fonts.googleapis.com/css?family=Roboto:300,400,500); -------------------------------------------------------------------------------- /assets/src/scss/_shadows.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | @mixin depth-0() { 21 | box-shadow: none; 22 | } 23 | 24 | @mixin depth-1() { 25 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); 26 | } 27 | 28 | @mixin depth-2() { 29 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.16), 0 4px 6px rgba(0, 0, 0, 0.23); 30 | } 31 | 32 | @mixin depth-3() { 33 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23); 34 | } 35 | 36 | @mixin depth-4() { 37 | box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22); 38 | } 39 | 40 | @mixin depth-5() { 41 | box-shadow: 0 19px 38px rgba(0, 0, 0, 0.30), 0 15px 12px rgba(0, 0, 0, 0.22); 42 | } 43 | 44 | @mixin depth($depth: 0) { 45 | @if $depth == 1 { 46 | @include depth-1(); 47 | } @else if $depth == 2 { 48 | @include depth-2(); 49 | } @else if $depth == 3 { 50 | @include depth-3(); 51 | } @else if $depth == 4 { 52 | @include depth-4(); 53 | } @else if $depth == 5 { 54 | @include depth-5(); 55 | } @else { 56 | @include depth-0(); 57 | } 58 | } 59 | 60 | .depth-0 { 61 | @include depth(0); 62 | } 63 | 64 | .depth-1 { 65 | @include depth(1); 66 | } 67 | 68 | .depth-2 { 69 | @include depth(2); 70 | } 71 | 72 | .depth-3 { 73 | @include depth(3); 74 | } 75 | 76 | .depth-4 { 77 | @include depth(4); 78 | } 79 | 80 | .depth-5 { 81 | @include depth(5); 82 | } 83 | -------------------------------------------------------------------------------- /assets/src/scss/_structure.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | html { 21 | height: 100%; 22 | } 23 | 24 | body { 25 | position: relative; 26 | margin: 0; 27 | padding-bottom: 200px; 28 | min-height: 100%; 29 | font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; 30 | overflow-x: hidden; 31 | overflow-y: scroll; 32 | background-color: $colorBackground; 33 | color: $colorText; 34 | } 35 | 36 | .footer { 37 | position: absolute; 38 | right: 0; 39 | bottom: 0; 40 | left: 0; 41 | } 42 | 43 | .modal-open .footer { 44 | padding-right: $scrollbarWidth; 45 | } 46 | 47 | .jumbotron { 48 | background-color: $colorForeground; 49 | border-radius: 0 !important; 50 | @include depth(1); 51 | } 52 | 53 | @media (max-width: 768px) { 54 | .jumbotron { 55 | box-shadow: none !important; 56 | } 57 | 58 | body { 59 | background-color: $colorForeground; 60 | } 61 | } 62 | 63 | .jumbotron p { 64 | font-size: 14px; 65 | } 66 | 67 | .content { 68 | padding-top: 120px; 69 | opacity: 1; 70 | transition: opacity 300ms ease; 71 | } 72 | 73 | .content-hidden { 74 | opacity: 0; 75 | transition: opacity 300ms ease 1s; 76 | } 77 | 78 | .force-select { 79 | -webkit-touch-callout: default; 80 | -webkit-user-select: auto; 81 | -moz-user-select: all; 82 | -ms-user-select: all; 83 | user-select: all; 84 | cursor: auto; 85 | } 86 | 87 | .row-margin { 88 | margin-top: 10px; 89 | } 90 | -------------------------------------------------------------------------------- /assets/src/scss/application.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | // Addons 21 | @import "../../../node_modules/awesome-sass-easing/sass-easing"; 22 | @import "../../../node_modules/@passypw/wavesjs/src/waves"; 23 | 24 | @import "base"; 25 | @import "shadows"; 26 | @import "structure"; 27 | @import "animations"; 28 | @import "components/all"; 29 | -------------------------------------------------------------------------------- /assets/src/scss/components/_all.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | @import "buttons"; 20 | @import "dropdown"; 21 | @import "inputs"; 22 | @import "modals"; 23 | @import "misc"; 24 | @import "navbar"; 25 | @import "snackbar"; 26 | -------------------------------------------------------------------------------- /assets/src/scss/components/_buttons.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | button { 21 | align-items: unset; 22 | } 23 | 24 | .btn > * { 25 | cursor: pointer; 26 | } 27 | 28 | .btn-default, .btn-default:focus, .btn-default:hover, .btn-default:focus:active { 29 | color: $colorText; 30 | background: $colorDefault; 31 | } 32 | 33 | .btn-default:active, .btn-default:hover:active { 34 | background: mix($colorDefault, $colorText, 70%); 35 | } 36 | 37 | .btn-primary, .btn-primary:focus, .btn-primary:hover, .btn-primary:focus:active { 38 | color: invert($colorText); 39 | background: $colorPrimary; 40 | } 41 | 42 | .btn-primary:active, .btn-primary:hover:active { 43 | background: mix($colorPrimary, invert($colorText), 70%); 44 | } 45 | 46 | .btn-success, .btn-success:focus, .btn-success:hover, .btn-success:focus:active { 47 | color: invert($colorText); 48 | background: $colorSuccess; 49 | } 50 | 51 | .btn-success:active, .btn-success:hover:active { 52 | background: mix($colorSuccess, invert($colorText), 70%); 53 | } 54 | 55 | .btn-info, .btn-info:focus, .btn-info:hover, .btn-info:focus:active { 56 | color: invert($colorText); 57 | background: $colorInfo; 58 | } 59 | 60 | .btn-info:active, .btn-info:hover:active { 61 | background: mix($colorInfo, invert($colorText), 70%); 62 | } 63 | 64 | .btn-warning, .btn-warning:focus, .btn-warning:hover, .btn-warning:focus:active { 65 | color: invert($colorText); 66 | background: $colorWarning; 67 | } 68 | 69 | .btn-warning:active, .btn-warning:hover:active { 70 | background: mix($colorWarning, invert($colorText), 70%); 71 | } 72 | 73 | .btn-danger, .btn-danger:focus, .btn-danger:hover, .btn-danger:focus:active { 74 | color: invert($colorText); 75 | background: $colorDanger; 76 | } 77 | 78 | .btn-danger:active, .btn-danger:hover:active { 79 | background: mix($colorDanger, invert($colorText), 70%); 80 | } 81 | 82 | .btn-flat, .btn-flat:focus, .btn-flat:hover, .btn-flat:active, .btn-flat:hover:active, .btn-flat[disabled], .btn-flat[disabled]:focus, .btn-flat[disabled]:hover, .btn-flat[disabled]:active, .btn-flat[disabled]:hover:active { 83 | background: transparent; 84 | outline: none !important; 85 | box-shadow: none; 86 | text-transform: uppercase; 87 | } 88 | 89 | .btn-flat.btn-default, .btn-flat.btn-default:focus, .btn-flat.btn-default:hover { 90 | color: $colorText; 91 | } 92 | 93 | .btn-flat.btn-default:active, .btn-flat.btn-default:active:hover { 94 | background: mix(transparent, $colorText, 70%); 95 | } 96 | 97 | .btn-flat.btn-primary, .btn-flat.btn-primary:focus, .btn-flat.btn-primary:hover { 98 | color: $colorPrimary; 99 | } 100 | 101 | .btn-flat.btn-primary:active, .btn-flat.btn-primary:active:hover { 102 | background: mix(transparent, $colorPrimary, 70%); 103 | } 104 | 105 | .btn-flat.btn-success, .btn-flat.btn-success:focus, .btn-flat.btn-success:hover { 106 | color: $colorSuccess; 107 | } 108 | 109 | .btn-flat.btn-success:active, .btn-flat.btn-success:active:hover { 110 | background: mix(transparent, $colorSuccess, 70%); 111 | } 112 | 113 | .btn-flat.btn-info, .btn-flat.btn-info:focus, .btn-flat.btn-info:hover { 114 | color: $colorInfo; 115 | } 116 | 117 | .btn-flat.btn-info:active, .btn-flat.btn-info:active:hover { 118 | background: mix(transparent, $colorInfo, 70%); 119 | } 120 | 121 | .btn-flat.btn-warning, .btn-flat.btn-warning:focus, .btn-flat.btn-warning:hover { 122 | color: $colorWarning; 123 | } 124 | 125 | .btn-flat.btn-warning:active, .btn-warning.btn-warning:active:hover { 126 | background: mix(transparent, $colorWarning, 70%); 127 | } 128 | 129 | .btn-flat.btn-danger, .btn-flat.btn-danger:focus, .btn-flat.btn-danger:hover { 130 | color: $colorDanger; 131 | } 132 | 133 | .btn-flat.btn-danger:active, .btn-info.btn-danger:active:hover { 134 | background: mix(transparent, $colorDanger, 70%); 135 | } 136 | 137 | .btn { 138 | height: 36px; 139 | min-width: 64px; 140 | padding-left: 16px; 141 | padding-right: 16px; 142 | margin: 5px; 143 | border: none; 144 | border-radius: 2px; 145 | text-transform: uppercase; 146 | font-size: 14px; 147 | transition: background 300ms $easeOutCubic, box-shadow 300ms $easeOutCubic; 148 | } 149 | 150 | .btn-flat { 151 | transition: background 300ms $easeOutCubic 100ms; 152 | } 153 | 154 | .btn:focus, .btn:active, .btn:focus:active { 155 | outline: none; 156 | } 157 | 158 | .btn:not(.btn-flat) { 159 | @include depth(1); 160 | } 161 | 162 | .btn:not(.btn-flat):hover { 163 | @include depth(2); 164 | } 165 | 166 | .container { 167 | position: relative; 168 | } 169 | 170 | .btn-fab { 171 | position: fixed; 172 | right: 20px; 173 | bottom: 20px; 174 | background: $colorAccent; 175 | padding-left: 0; 176 | padding-right: 0; 177 | color: $colorAccentText; 178 | height: 56px; 179 | width: 56px; 180 | min-width: initial; 181 | border-radius: 100%; 182 | z-index: 999; 183 | } 184 | 185 | .btn-fab:focus, .btn-fab:hover { 186 | color: $colorAccentText; 187 | } 188 | 189 | .btn-fab:active, .btn-fab:active:hover { 190 | background-color: mix($colorAccent, $colorAccentText, 70%); 191 | } 192 | 193 | .modal-open .btn-fab { 194 | right: $scrollbarWidth + 20px; 195 | } 196 | 197 | a.btn-block { 198 | width: auto; 199 | } 200 | -------------------------------------------------------------------------------- /assets/src/scss/components/_dropdown.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | .dropdown-toggle { 21 | min-width: 36px; 22 | width: 36px; 23 | height: 36px; 24 | padding-left: 0; 25 | padding-right: 0; 26 | border-radius: 100%; 27 | } 28 | 29 | .open .dropdown-menu { 30 | position: absolute; 31 | float: left; 32 | width: 200px; 33 | margin-top: 0; 34 | background-color: $colorForeground; 35 | border-radius: 0; 36 | border: none; 37 | top: 2px; 38 | right: 2px; 39 | @include depth(3); 40 | } 41 | 42 | .dropdown.open .dropdown-menu { 43 | animation: dropdownIn 200ms $easeOutQuart; 44 | } 45 | 46 | .dropdown.open .dropdown-menu > li { 47 | opacity: 0; 48 | animation: dropdownItemsIn 300ms $easeOutQuart forwards; 49 | } 50 | 51 | @media (max-width: 767px) { 52 | .open .dropdown-menu > li > a { 53 | color: $colorText; 54 | } 55 | .open .dropdown-menu > li > a { 56 | line-height: 1.42857143; 57 | } 58 | .open .dropdown-menu .dropdown-header, .open .dropdown-menu > li > a { 59 | padding: 3px 20px; 60 | } 61 | 62 | .open .dropdown-menu { 63 | position: absolute; 64 | float: left; 65 | width: 200px; 66 | background-color: $colorForeground; 67 | border: none; 68 | } 69 | } 70 | 71 | .navbar-nav .open .dropdown-menu, .contextmenu.open .dropdown-menu { 72 | position: absolute; 73 | float: left; 74 | width: 200px; 75 | margin-top: 0; 76 | background-color: $colorForeground; 77 | border-radius: 0; 78 | border: none; 79 | top: 2px; 80 | right: 2px; 81 | @include depth(3); 82 | } 83 | 84 | .dropdown.open .dropdown-menu { 85 | animation: dropdownIn 200ms $easeOutQuart; 86 | } 87 | 88 | .dropdown.open .dropdown-menu > li { 89 | opacity: 0; 90 | animation: dropdownItemsIn 300ms $easeOutQuart forwards; 91 | } 92 | 93 | @media (max-width: 767px) { 94 | .navbar-default .navbar-nav .open .dropdown-menu > li > a { 95 | color: $colorText; 96 | } 97 | .navbar-nav .open .dropdown-menu > li > a { 98 | line-height: 1.42857143; 99 | } 100 | .navbar-nav .open .dropdown-menu .dropdown-header, .navbar-nav .open .dropdown-menu > li > a { 101 | padding: 3px 20px; 102 | } 103 | 104 | .navbar-nav .open .dropdown-menu { 105 | position: absolute; 106 | float: left; 107 | width: 200px; 108 | background-color: $colorForeground; 109 | border: none; 110 | } 111 | } 112 | 113 | .contextmenu { 114 | position: fixed; 115 | z-index: 9000; 116 | top: 0; 117 | left: 0; 118 | } 119 | 120 | .contextmenu.open > .dropdown-menu { 121 | animation: contextMenuIn 200ms $easeOutQuart; 122 | } 123 | 124 | .contextmenu > .dropdown-menu { 125 | @include depth(5); 126 | } 127 | -------------------------------------------------------------------------------- /assets/src/scss/components/_inputs.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | label { 21 | font-weight: normal; 22 | } 23 | 24 | .text { 25 | padding-top: 15px; 26 | } 27 | 28 | .text:last-of-type { 29 | padding-bottom: 15px; 30 | } 31 | 32 | .text .form-control { 33 | height: 24px; 34 | display: block; 35 | padding: 0; 36 | border: none; 37 | border-bottom: solid rgba(0, 0, 0, 0.12) 2px; 38 | border-radius: 0; 39 | box-shadow: none; 40 | line-height: 24px; 41 | font-size: 14px; 42 | margin: 5px 0 0; 43 | } 44 | 45 | .text > label { 46 | color: $colorSecondaryText; 47 | font-size: 14px; 48 | position: absolute; 49 | pointer-events: none; 50 | transform-origin: bottom left; 51 | transform: translateY(-24px); 52 | transition: all 200ms $easeOutQuart; 53 | } 54 | 55 | .text > .form-control:focus ~ label, .text > .form-control.hastext ~ label { 56 | transform: scale(.8) translateY(-53px); 57 | color: $colorAccent; 58 | } 59 | 60 | .text > .form-control:focus { 61 | box-shadow: none; 62 | border-bottom-color: $colorAccent; 63 | } 64 | 65 | .text > .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { 66 | background-color: transparent; 67 | opacity: 0.5; 68 | } 69 | 70 | .text.hasbtn > .btn-input { 71 | display: block; 72 | background-color: transparent; 73 | border: none; 74 | outline: none; 75 | float: right; 76 | transform-origin: bottom left; 77 | border-radius: 100%; 78 | padding: 2px; 79 | transform: translateY(-28px); 80 | transition: background 300ms linear; 81 | } 82 | 83 | .text.hasbtn > .btn-input:active { 84 | background-color: mix(transparent, $colorText, 70%) !important; 85 | } 86 | 87 | .text.hasbtn > .form-control { 88 | padding-right: 30px; 89 | } 90 | -------------------------------------------------------------------------------- /assets/src/scss/components/_misc.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | .progress { 21 | border: none; 22 | border-radius: 0; 23 | box-shadow: none; 24 | background: transparent; 25 | } 26 | 27 | .progress-bar { 28 | background-color: $colorPrimary; 29 | } 30 | 31 | .progress-bar-success { 32 | background-color: $colorSuccess; 33 | } 34 | 35 | .progress-bar-info { 36 | background-color: $colorInfo; 37 | } 38 | 39 | .progress-bar-warning { 40 | background-color: $colorWarning; 41 | } 42 | 43 | .progress-bar-danger { 44 | background-color: $colorDanger; 45 | } 46 | 47 | .progress-bar { 48 | transition: width 600ms $easeInOutQuart; 49 | } 50 | 51 | .spinner { 52 | animation: spinnerRotate $durationSpinner linear infinite; 53 | } 54 | 55 | .spinner .path { 56 | stroke-dasharray: $offsetSpinnerStroke; 57 | stroke-dashoffset: 0; 58 | stroke: $colorText; 59 | transform-origin: center center; 60 | animation: spinnerDash $durationSpinner $easeInOutSine infinite; 61 | } 62 | 63 | .spinner.primary .path { 64 | stroke: $colorAccent; 65 | } 66 | 67 | .load-spinner { 68 | position: fixed; 69 | left: 50%; 70 | color: $colorForeground; 71 | border-radius: 100%; 72 | height: 40px; 73 | width: 40px; 74 | padding: 10px; 75 | transform: translate(-50%, -100%) scale(0); 76 | transition: transform 200ms $easeOutQuart; 77 | @include depth(2); 78 | } 79 | 80 | .load-spinner.shown { 81 | transform: translate(-50%, 0) scale(1); 82 | } 83 | 84 | .material-icons { 85 | vertical-align: middle; 86 | font-size: 24px; 87 | direction: ltr; 88 | } 89 | 90 | .table > tbody > tr:after { 91 | content: ''; 92 | display: inline-block; 93 | height: 64px; 94 | width: 0; 95 | } 96 | 97 | .table td, .table th { 98 | vertical-align: middle !important; 99 | border-top: 1px solid $colorDivider !important; 100 | font-weight: normal !important; 101 | } 102 | 103 | .table th { 104 | color: rgba($colorText, .75); 105 | } 106 | 107 | .table td.number { 108 | text-align: right; 109 | } 110 | 111 | .table > thead > tr { 112 | height: 56px; 113 | } 114 | 115 | .table > tbody:empty { 116 | position: relative; 117 | } 118 | 119 | .table > tbody:empty::after { 120 | content: 'Empty'; 121 | position: absolute; 122 | left: 50%; 123 | transform: translateX(-50%); 124 | text-align: center; 125 | color: rgba($colorText, .75); 126 | } 127 | 128 | @media screen and (max-width: 767px) { 129 | .table-responsive { 130 | border: none; 131 | } 132 | } 133 | 134 | .g-recaptcha { 135 | padding-top: 15px; 136 | } 137 | 138 | .margin-center { 139 | margin: 0 auto; 140 | } 141 | -------------------------------------------------------------------------------- /assets/src/scss/components/_modals.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | .modal.fade, .modal.in, .modal.fade .modal-dialog, .modal.in .modal-dialog { 21 | transform: none; 22 | transition: opacity 100ms linear; 23 | } 24 | 25 | .modal-backdrop.in { 26 | opacity: 0.2; 27 | } 28 | 29 | .modal-header { 30 | background-color: $colorForeground; 31 | border-bottom: none; 32 | padding: 24px 24px 0 24px; 33 | border-radius: 2px; 34 | } 35 | 36 | .modal-title { 37 | color: $colorText; 38 | } 39 | 40 | .modal-body { 41 | padding: 20px 24px 24px 24px; 42 | } 43 | 44 | .modal-body > p { 45 | color: lighten($colorText, 20%); 46 | } 47 | 48 | .modal-footer { 49 | border-top: none; 50 | padding: 8px 8px 8px 24px; 51 | } 52 | 53 | .modal-footer > .btn-flat { 54 | padding-left: 8px; 55 | padding-right: 8px; 56 | margin: 0 0 0 8px !important; 57 | } 58 | 59 | .modal-content { 60 | border-radius: 2px; 61 | border: none; 62 | @include depth(4); 63 | } 64 | -------------------------------------------------------------------------------- /assets/src/scss/components/_navbar.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | .navbar-default { 21 | background-color: $colorTheme; 22 | border: none; 23 | border-radius: 0; 24 | overflow-y: hidden; 25 | @include depth(2); 26 | } 27 | 28 | .navbar-default .navbar-brand, .navbar-default .navbar-brand:focus, .navbar-default .navbar-brand:hover, 29 | .navbar-default .navbar-nav > li > a, .navbar-default .navbar-nav > .active > a, 30 | .navbar-default .navbar-nav > .active > a:focus, .navbar-default .navbar-nav > .active > a:hover { 31 | color: $colorThemeText; 32 | } 33 | 34 | .navbar-default .navbar-nav > li > a:active, .navbar-default .navbar-nav > li > a:active:hover { 35 | background: mix(transparent, $colorThemeText, 70%); 36 | } 37 | 38 | .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:focus, .navbar-default .navbar-nav > .active > a:hover { 39 | background-color: transparent; 40 | border-bottom: solid 2px $colorThemeText; 41 | } 42 | 43 | .navbar-default .navbar-nav > li > a { 44 | background: transparent; 45 | border: none; 46 | border-bottom: solid 2px transparent; 47 | transition: border 200ms ease, background 300ms linear; 48 | } 49 | 50 | .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, 51 | .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus, .navbar-default .navbar-nav > li > a:hover { 52 | color: $colorThemeText; 53 | background-color: transparent; 54 | border-bottom: solid 2px $colorThemeText; 55 | } 56 | 57 | .navbar-right > li > a, .navbar-right > li > a:focus, .navbar-right > li > a:hover, 58 | .navbar-right > .dropdown > a, .navbar-right > .dropdown > a:focus, .navbar-right > .dropdown > a:hover, 59 | .navbar-right > .open > a, .navbar-right > .open > a:focus, .navbar-right > .open > a:hover { 60 | color: $colorThemeText !important; 61 | background-color: transparent !important; 62 | border-radius: 100%; 63 | border-bottom: none !important; 64 | width: 44px; 65 | height: 44px; 66 | padding: 10px; 67 | margin: 5px; 68 | } 69 | 70 | .navbar-right > li > a:active, .navbar-right > li > a:active:hover, 71 | .navbar-right > .dropdown > a:active, .navbar-right > .dropdown > a:active:hover, 72 | .navbar-right > .open > a:active, .navbar-right > .open > a:active:hover { 73 | background-color: mix(transparent, $colorThemeText, 70%) !important; 74 | } 75 | 76 | .navbar-nav { 77 | margin: 0 auto; 78 | display: table; 79 | table-layout: fixed; 80 | float: none; 81 | } 82 | 83 | .navbar-nav > li { 84 | float: left; 85 | } 86 | 87 | .navbar-right { 88 | float: right !important; 89 | margin-right: -15px; 90 | } 91 | 92 | .container-fluid > .navbar-collapse, .container-fluid > .navbar-header, .container > .navbar-collapse, .container > .navbar-header { 93 | margin-right: 0; 94 | margin-left: 0; 95 | } 96 | 97 | .navbar-header { 98 | float: left; 99 | } 100 | 101 | .navbar-right .dropdown-menu { 102 | top: 2px; 103 | right: 2px; 104 | left: auto; 105 | } 106 | 107 | .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:focus, .nav-tabs.nav-justified > .active > a:hover { 108 | border: none; 109 | border-radius: 0; 110 | } 111 | 112 | .nav-tabs.nav-justified > li { 113 | display: table-cell; 114 | width: 1%; 115 | } 116 | 117 | .nav-tabs.nav-justified > li > a { 118 | border-bottom: none; 119 | border-radius: 0; 120 | margin-bottom: 0; 121 | } 122 | 123 | .tab-pane { 124 | padding: 20px 20px; 125 | } 126 | 127 | .modal-open .navbar-fixed-top { 128 | padding-right: $scrollbarWidth; 129 | } -------------------------------------------------------------------------------- /assets/src/scss/components/_snackbar.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | .snackbar { 21 | position: fixed; 22 | width: auto; 23 | min-width: 288px; 24 | max-width: 568px; 25 | padding: 10px 24px 10px 24px; 26 | border-radius: 2px; 27 | left: 20px; 28 | color: #fff; 29 | background: #212121; 30 | z-index: 1000; 31 | transition: bottom 300ms $easeInExpo; 32 | } 33 | 34 | .snackbar.snackbar-show { 35 | transition: bottom 300ms $easeOutQuart; 36 | } 37 | 38 | .snackbar > .btn-flat { 39 | margin-top: -6px; 40 | margin-right: -6px; 41 | margin-bottom: -6px; 42 | float: right; 43 | } 44 | 45 | @media screen and (max-width: 768px) { 46 | .snackbar { 47 | width: 100%; 48 | max-width: none; 49 | left: 0; 50 | border-radius: 0; 51 | } 52 | 53 | .snackbar.snackbar-show { 54 | bottom: 0 !important; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | #ff5722 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passy.pw/passy", 3 | "description": "A password manager written in PHP to serve you over the internet.", 4 | "repositories": [ 5 | { 6 | "type": "vcs", 7 | "url": "https://github.com/PASSYpw/PASSY" 8 | } 9 | ], 10 | "support": { 11 | "issues": "https://github.com/PASSYpw/PASSY/issues" 12 | }, 13 | "license": "Apache-2.0", 14 | "authors": [ 15 | { 16 | "name": "Scrumplex", 17 | "email": "contact@scrumplex.net" 18 | }, 19 | { 20 | "name": "Liz3", 21 | "email": "info@liz3.de" 22 | } 23 | ], 24 | "minimum-stability": "stable", 25 | "require": { 26 | "php": ">=7.0.10", 27 | "google/recaptcha": "1.1.2", 28 | "defuse/php-encryption": "2.0.3", 29 | "league/csv": "^9.0", 30 | "pragmarx/google2fa": "v1.0.1", 31 | "bacon/bacon-qr-code": "1.0.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "0a6bb64b8862f5e7b954815859a02415", 8 | "content-hash": "4fd90403004ddbbf0ad6f502b4b5fa38", 9 | "packages": [ 10 | { 11 | "name": "bacon/bacon-qr-code", 12 | "version": "1.0.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/Bacon/BaconQrCode.git", 16 | "reference": "031a2ce68c5794064b49d11775b2daf45c96e21c" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/031a2ce68c5794064b49d11775b2daf45c96e21c", 21 | "reference": "031a2ce68c5794064b49d11775b2daf45c96e21c", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.3.3" 26 | }, 27 | "suggest": { 28 | "ext-gd": "to generate QR code images" 29 | }, 30 | "type": "library", 31 | "autoload": { 32 | "psr-0": { 33 | "BaconQrCode": "src/" 34 | } 35 | }, 36 | "notification-url": "https://packagist.org/downloads/", 37 | "license": [ 38 | "BSD-2-Clause" 39 | ], 40 | "authors": [ 41 | { 42 | "name": "Ben Scholzen 'DASPRiD'", 43 | "email": "mail@dasprids.de", 44 | "homepage": "http://www.dasprids.de", 45 | "role": "Developer" 46 | } 47 | ], 48 | "description": "BaconQrCode is a QR code generator for PHP.", 49 | "homepage": "https://github.com/Bacon/BaconQrCode", 50 | "time": "2016-01-09 22:55:35" 51 | }, 52 | { 53 | "name": "christian-riesen/base32", 54 | "version": "1.3.1", 55 | "source": { 56 | "type": "git", 57 | "url": "https://github.com/ChristianRiesen/base32.git", 58 | "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa" 59 | }, 60 | "dist": { 61 | "type": "zip", 62 | "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", 63 | "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", 64 | "shasum": "" 65 | }, 66 | "require": { 67 | "php": ">=5.3.0" 68 | }, 69 | "require-dev": { 70 | "phpunit/phpunit": "4.*", 71 | "satooshi/php-coveralls": "0.*" 72 | }, 73 | "type": "library", 74 | "extra": { 75 | "branch-alias": { 76 | "dev-master": "1.1.x-dev" 77 | } 78 | }, 79 | "autoload": { 80 | "psr-4": { 81 | "Base32\\": "src/" 82 | } 83 | }, 84 | "notification-url": "https://packagist.org/downloads/", 85 | "license": [ 86 | "MIT" 87 | ], 88 | "authors": [ 89 | { 90 | "name": "Christian Riesen", 91 | "email": "chris.riesen@gmail.com", 92 | "homepage": "http://christianriesen.com", 93 | "role": "Developer" 94 | } 95 | ], 96 | "description": "Base32 encoder/decoder according to RFC 4648", 97 | "homepage": "https://github.com/ChristianRiesen/base32", 98 | "keywords": [ 99 | "base32", 100 | "decode", 101 | "encode", 102 | "rfc4648" 103 | ], 104 | "time": "2016-05-05 11:49:03" 105 | }, 106 | { 107 | "name": "defuse/php-encryption", 108 | "version": "2.0.3", 109 | "source": { 110 | "type": "git", 111 | "url": "https://github.com/defuse/php-encryption.git", 112 | "reference": "2c6fea3d9a4eaaa8cef86b2a89f3660818117b33" 113 | }, 114 | "dist": { 115 | "type": "zip", 116 | "url": "https://api.github.com/repos/defuse/php-encryption/zipball/2c6fea3d9a4eaaa8cef86b2a89f3660818117b33", 117 | "reference": "2c6fea3d9a4eaaa8cef86b2a89f3660818117b33", 118 | "shasum": "" 119 | }, 120 | "require": { 121 | "ext-openssl": "*", 122 | "paragonie/random_compat": "~2.0", 123 | "php": ">=5.4.0" 124 | }, 125 | "require-dev": { 126 | "nikic/php-parser": "^2.0" 127 | }, 128 | "type": "library", 129 | "autoload": { 130 | "classmap": [ 131 | "src" 132 | ] 133 | }, 134 | "notification-url": "https://packagist.org/downloads/", 135 | "license": [ 136 | "MIT" 137 | ], 138 | "authors": [ 139 | { 140 | "name": "Taylor Hornby", 141 | "email": "taylor@defuse.ca", 142 | "homepage": "https://defuse.ca/" 143 | }, 144 | { 145 | "name": "Scott Arciszewski", 146 | "email": "info@paragonie.com", 147 | "homepage": "https://paragonie.com" 148 | } 149 | ], 150 | "description": "Secure PHP Encryption Library", 151 | "keywords": [ 152 | "aes", 153 | "authenticated encryption", 154 | "cipher", 155 | "crypto", 156 | "cryptography", 157 | "encrypt", 158 | "encryption", 159 | "openssl", 160 | "security", 161 | "symmetric key cryptography" 162 | ], 163 | "time": "2016-10-10 15:20:26" 164 | }, 165 | { 166 | "name": "google/recaptcha", 167 | "version": "1.1.2", 168 | "source": { 169 | "type": "git", 170 | "url": "https://github.com/google/recaptcha.git", 171 | "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e" 172 | }, 173 | "dist": { 174 | "type": "zip", 175 | "url": "https://api.github.com/repos/google/recaptcha/zipball/2b7e00566afca82a38a1d3adb8e42c118006296e", 176 | "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e", 177 | "shasum": "" 178 | }, 179 | "require": { 180 | "php": ">=5.3.2" 181 | }, 182 | "require-dev": { 183 | "phpunit/phpunit": "4.5.*" 184 | }, 185 | "type": "library", 186 | "extra": { 187 | "branch-alias": { 188 | "dev-master": "1.1.x-dev" 189 | } 190 | }, 191 | "autoload": { 192 | "psr-4": { 193 | "ReCaptcha\\": "src/ReCaptcha" 194 | } 195 | }, 196 | "notification-url": "https://packagist.org/downloads/", 197 | "license": [ 198 | "BSD-3-Clause" 199 | ], 200 | "description": "Client library for reCAPTCHA, a free service that protect websites from spam and abuse.", 201 | "homepage": "http://www.google.com/recaptcha/", 202 | "keywords": [ 203 | "Abuse", 204 | "captcha", 205 | "recaptcha", 206 | "spam" 207 | ], 208 | "time": "2015-09-02 17:23:59" 209 | }, 210 | { 211 | "name": "league/csv", 212 | "version": "9.0.1", 213 | "source": { 214 | "type": "git", 215 | "url": "https://github.com/thephpleague/csv.git", 216 | "reference": "5dc305e7958190bcab0cc2778888a4f658d29aa1" 217 | }, 218 | "dist": { 219 | "type": "zip", 220 | "url": "https://api.github.com/repos/thephpleague/csv/zipball/5dc305e7958190bcab0cc2778888a4f658d29aa1", 221 | "reference": "5dc305e7958190bcab0cc2778888a4f658d29aa1", 222 | "shasum": "" 223 | }, 224 | "require": { 225 | "ext-mbstring": "*", 226 | "php": ">=7.0.10" 227 | }, 228 | "require-dev": { 229 | "ext-curl": "*", 230 | "friendsofphp/php-cs-fixer": "^2.0", 231 | "phpunit/phpunit": "^6.0" 232 | }, 233 | "suggest": { 234 | "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" 235 | }, 236 | "type": "library", 237 | "extra": { 238 | "branch-alias": { 239 | "dev-master": "9.x-dev" 240 | } 241 | }, 242 | "autoload": { 243 | "psr-4": { 244 | "League\\Csv\\": "src" 245 | }, 246 | "files": [ 247 | "src/functions_include.php" 248 | ] 249 | }, 250 | "notification-url": "https://packagist.org/downloads/", 251 | "license": [ 252 | "MIT" 253 | ], 254 | "authors": [ 255 | { 256 | "name": "Ignace Nyamagana Butera", 257 | "email": "nyamsprod@gmail.com", 258 | "homepage": "https://github.com/nyamsprod/", 259 | "role": "Developer" 260 | } 261 | ], 262 | "description": "Csv data manipulation made easy in PHP", 263 | "homepage": "http://csv.thephpleague.com", 264 | "keywords": [ 265 | "csv", 266 | "export", 267 | "filter", 268 | "import", 269 | "read", 270 | "write" 271 | ], 272 | "time": "2017-08-21 13:42:10" 273 | }, 274 | { 275 | "name": "paragonie/random_compat", 276 | "version": "v2.0.10", 277 | "source": { 278 | "type": "git", 279 | "url": "https://github.com/paragonie/random_compat.git", 280 | "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" 281 | }, 282 | "dist": { 283 | "type": "zip", 284 | "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", 285 | "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", 286 | "shasum": "" 287 | }, 288 | "require": { 289 | "php": ">=5.2.0" 290 | }, 291 | "require-dev": { 292 | "phpunit/phpunit": "4.*|5.*" 293 | }, 294 | "suggest": { 295 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." 296 | }, 297 | "type": "library", 298 | "autoload": { 299 | "files": [ 300 | "lib/random.php" 301 | ] 302 | }, 303 | "notification-url": "https://packagist.org/downloads/", 304 | "license": [ 305 | "MIT" 306 | ], 307 | "authors": [ 308 | { 309 | "name": "Paragon Initiative Enterprises", 310 | "email": "security@paragonie.com", 311 | "homepage": "https://paragonie.com" 312 | } 313 | ], 314 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", 315 | "keywords": [ 316 | "csprng", 317 | "pseudorandom", 318 | "random" 319 | ], 320 | "time": "2017-03-13 16:27:32" 321 | }, 322 | { 323 | "name": "pragmarx/google2fa", 324 | "version": "v1.0.1", 325 | "source": { 326 | "type": "git", 327 | "url": "https://github.com/antonioribeiro/google2fa.git", 328 | "reference": "b346dc138339b745c5831405d00cff7c1351aa0d" 329 | }, 330 | "dist": { 331 | "type": "zip", 332 | "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/b346dc138339b745c5831405d00cff7c1351aa0d", 333 | "reference": "b346dc138339b745c5831405d00cff7c1351aa0d", 334 | "shasum": "" 335 | }, 336 | "require": { 337 | "christian-riesen/base32": "~1.3", 338 | "paragonie/random_compat": "~1.4|~2.0", 339 | "php": ">=5.4", 340 | "symfony/polyfill-php56": "~1.2" 341 | }, 342 | "require-dev": { 343 | "phpspec/phpspec": "~2.1" 344 | }, 345 | "suggest": { 346 | "bacon/bacon-qr-code": "Required to generate inline QR Codes." 347 | }, 348 | "type": "library", 349 | "extra": { 350 | "component": "package", 351 | "frameworks": [ 352 | "Laravel" 353 | ], 354 | "branch-alias": { 355 | "dev-master": "1.0-dev" 356 | } 357 | }, 358 | "autoload": { 359 | "psr-4": { 360 | "PragmaRX\\Google2FA\\": "src/" 361 | } 362 | }, 363 | "notification-url": "https://packagist.org/downloads/", 364 | "license": [ 365 | "BSD-3-Clause" 366 | ], 367 | "authors": [ 368 | { 369 | "name": "Antonio Carlos Ribeiro", 370 | "email": "acr@antoniocarlosribeiro.com", 371 | "role": "Creator & Designer" 372 | } 373 | ], 374 | "description": "A One Time Password Authentication package, compatible with Google Authenticator.", 375 | "keywords": [ 376 | "Authentication", 377 | "Two Factor Authentication", 378 | "google2fa", 379 | "laravel" 380 | ], 381 | "time": "2016-07-18 20:25:04" 382 | }, 383 | { 384 | "name": "symfony/polyfill-php56", 385 | "version": "v1.5.0", 386 | "source": { 387 | "type": "git", 388 | "url": "https://github.com/symfony/polyfill-php56.git", 389 | "reference": "e85ebdef569b84e8709864e1a290c40f156b30ca" 390 | }, 391 | "dist": { 392 | "type": "zip", 393 | "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/e85ebdef569b84e8709864e1a290c40f156b30ca", 394 | "reference": "e85ebdef569b84e8709864e1a290c40f156b30ca", 395 | "shasum": "" 396 | }, 397 | "require": { 398 | "php": ">=5.3.3", 399 | "symfony/polyfill-util": "~1.0" 400 | }, 401 | "type": "library", 402 | "extra": { 403 | "branch-alias": { 404 | "dev-master": "1.5-dev" 405 | } 406 | }, 407 | "autoload": { 408 | "psr-4": { 409 | "Symfony\\Polyfill\\Php56\\": "" 410 | }, 411 | "files": [ 412 | "bootstrap.php" 413 | ] 414 | }, 415 | "notification-url": "https://packagist.org/downloads/", 416 | "license": [ 417 | "MIT" 418 | ], 419 | "authors": [ 420 | { 421 | "name": "Nicolas Grekas", 422 | "email": "p@tchwork.com" 423 | }, 424 | { 425 | "name": "Symfony Community", 426 | "homepage": "https://symfony.com/contributors" 427 | } 428 | ], 429 | "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", 430 | "homepage": "https://symfony.com", 431 | "keywords": [ 432 | "compatibility", 433 | "polyfill", 434 | "portable", 435 | "shim" 436 | ], 437 | "time": "2017-06-14 15:44:48" 438 | }, 439 | { 440 | "name": "symfony/polyfill-util", 441 | "version": "v1.5.0", 442 | "source": { 443 | "type": "git", 444 | "url": "https://github.com/symfony/polyfill-util.git", 445 | "reference": "67925d1cf0b84bd234a83bebf26d4eb281744c6d" 446 | }, 447 | "dist": { 448 | "type": "zip", 449 | "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/67925d1cf0b84bd234a83bebf26d4eb281744c6d", 450 | "reference": "67925d1cf0b84bd234a83bebf26d4eb281744c6d", 451 | "shasum": "" 452 | }, 453 | "require": { 454 | "php": ">=5.3.3" 455 | }, 456 | "type": "library", 457 | "extra": { 458 | "branch-alias": { 459 | "dev-master": "1.5-dev" 460 | } 461 | }, 462 | "autoload": { 463 | "psr-4": { 464 | "Symfony\\Polyfill\\Util\\": "" 465 | } 466 | }, 467 | "notification-url": "https://packagist.org/downloads/", 468 | "license": [ 469 | "MIT" 470 | ], 471 | "authors": [ 472 | { 473 | "name": "Nicolas Grekas", 474 | "email": "p@tchwork.com" 475 | }, 476 | { 477 | "name": "Symfony Community", 478 | "homepage": "https://symfony.com/contributors" 479 | } 480 | ], 481 | "description": "Symfony utilities for portability of PHP codes", 482 | "homepage": "https://symfony.com", 483 | "keywords": [ 484 | "compat", 485 | "compatibility", 486 | "polyfill", 487 | "shim" 488 | ], 489 | "time": "2017-07-05 15:09:33" 490 | } 491 | ], 492 | "packages-dev": [], 493 | "aliases": [], 494 | "minimum-stability": "stable", 495 | "stability-flags": [], 496 | "prefer-stable": false, 497 | "prefer-lowest": false, 498 | "platform": { 499 | "php": ">=7.0.10" 500 | }, 501 | "platform-dev": [] 502 | } 503 | -------------------------------------------------------------------------------- /config.inc.php: -------------------------------------------------------------------------------- 1 | "localhost", 6 | "user" => "passy", 7 | "password" => "", 8 | "db" => "passy" 9 | 10 | ); 11 | 12 | $customizationConfig = array( 13 | "title" => "PASSY" // Will be shown in titlebar and footer. 14 | ); 15 | 16 | $generalConfig = array( 17 | 18 | "redirect_ssl" => false, // Redirects from HTTP to HTTPS. May not work on every setup. 19 | 20 | "security" => array( 21 | "lock_session_to_ip" => true // If your IP changes you will be logged out. This is necessary if there is a MITM and someone steals your cookie. 22 | ), 23 | 24 | "registration" => array( 25 | 26 | "enabled" => true // Enable / Disable registration. 27 | 28 | ), 29 | "login_history" => array( 30 | 31 | "enabled" => true // Logs IP and User-Agent on every login. 32 | 33 | ), 34 | "recaptcha" => array( 35 | "enabled" => false, // Prevent spam registrations 36 | "website_key" => "", 37 | "private_key" => "" 38 | ) 39 | 40 | ); -------------------------------------------------------------------------------- /examples/nginx_site.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | server_name _; 4 | 5 | root /var/www/passy; 6 | index index.php; 7 | 8 | location / { 9 | try_files $uri $uri/ =404; 10 | } 11 | 12 | location ~ /(vendor|src|examples)/ { 13 | deny all; 14 | return 403; 15 | } 16 | 17 | location ~ (composer\.json|\.gitignore|gulpfile\.js|\.lock|package\.json|\.md|\.inc\.php|Vagrantfile)$ { 18 | deny all; 19 | return 403; 20 | } 21 | 22 | location ~ \.php$ { 23 | try_files $uri =404; 24 | 25 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 26 | 27 | fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; 28 | fastcgi_index index.php; 29 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 30 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 31 | fastcgi_buffer_size 8m; 32 | fastcgi_buffers 1024 8m; 33 | fastcgi_busy_buffers_size 16m; 34 | fastcgi_temp_file_write_size 12m; 35 | 36 | include /etc/nginx/fastcgi.conf; 37 | } 38 | 39 | autoindex off; 40 | } 41 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * PASSY - Modern HTML5 Password Manager 3 | * Copyright (C) 2017 Sefa Eyeoglu (https://scrumplex.net) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | const gulp = require('gulp'); 21 | const pump = require('pump'); 22 | 23 | // CSS 24 | const postcss = require('gulp-postcss'); 25 | const sourcemaps = require('gulp-sourcemaps'); 26 | const sass = require('gulp-sass'); 27 | 28 | // JS 29 | const uglify = require('gulp-uglify'); 30 | const babel = require('gulp-babel'); 31 | 32 | gulp.task('css-prod', function (cb) { 33 | pump([ 34 | gulp.src('assets/src/scss/application.scss'), 35 | sourcemaps.init(), 36 | postcss([ 37 | require('autoprefixer'), 38 | require('postcss-csso') 39 | ], { 40 | syntax: require('postcss-scss') 41 | }), 42 | sass({outputStyle: 'compressed'}), 43 | sourcemaps.write(), 44 | gulp.dest('assets/css') 45 | ], 46 | cb); 47 | 48 | }); 49 | 50 | gulp.task('js-prod', function (cb) { 51 | pump([ 52 | gulp.src('assets/src/js/*.js'), 53 | sourcemaps.init(), 54 | babel({ 55 | presets: ['env'] 56 | }), 57 | uglify(), 58 | sourcemaps.write(), 59 | gulp.dest('assets/js') 60 | ], 61 | cb); 62 | 63 | }); 64 | 65 | gulp.task('css-dev', function (cb) { 66 | pump([ 67 | gulp.src('assets/src/scss/application.scss'), 68 | sourcemaps.init(), 69 | sass(), 70 | sourcemaps.write(), 71 | gulp.dest('assets/css') 72 | ], 73 | cb); 74 | 75 | }); 76 | 77 | gulp.task('js-dev', function (cb) { 78 | pump([ 79 | gulp.src('assets/src/js/*.js'), 80 | sourcemaps.init(), 81 | sourcemaps.write(), 82 | gulp.dest('assets/js') 83 | ], 84 | cb); 85 | 86 | }); 87 | 88 | gulp.task('default', ['css-prod', 'js-prod']); 89 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | <?= $customizationConfig["title"] ?> 43 | 44 | 45 | 46 | 47 | 48 | 120 | 121 | 127 | 128 |
129 |
130 | 131 | 132 | 133 |
134 | 135 | 150 | 151 |
152 | 153 | 172 | 173 | 174 | 183 | 184 | 213 | 214 | 215 | 216 | 217 | 220 | 221 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PASSY", 3 | "icons": [ 4 | { 5 | "src": "assets/img/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "assets/img/android-chrome-512x512.png", 11 | "sizes": "512x512", 12 | "type": "image/png" 13 | } 14 | ], 15 | "theme_color": "#ffffff", 16 | "background_color": "#ffffff", 17 | "display": "standalone" 18 | } 19 | -------------------------------------------------------------------------------- /meta.inc.php: -------------------------------------------------------------------------------- 1 | (https://scrumplex.net) 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | define("PASSY_BUILD", 205); 22 | define("PASSY_VERSION", "2.0.5"); 23 | define("PASSY_REPO", "https://github.com/PASSYpw/PASSY"); 24 | define("PASSY_BUGTRACKER", "https://github.com/PASSYpw/PASSY/issues"); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@passypw/passy", 3 | "version": "2.0.4", 4 | "description": "A password manager written in PHP to serve you over the internet.", 5 | "main": "index.php", 6 | "dependencies": { 7 | "@passypw/wavesjs": "^2.0.0", 8 | "bootstrap": "3.3.7", 9 | "jquery": "^3.2.1" 10 | }, 11 | "devDependencies": { 12 | "autoprefixer": "^7.1.4", 13 | "awesome-sass-easing": "^1.1.2", 14 | "babel-core": "^6.26.0", 15 | "babel-preset-env": "^1.6.1", 16 | "gulp": "^3.9.1", 17 | "gulp-babel": "^7.0.0", 18 | "gulp-postcss": "^7.0.0", 19 | "gulp-sass": "^3.1.0", 20 | "gulp-sourcemaps": "^2.6.1", 21 | "gulp-uglify": "^3.0.0", 22 | "postcss-cli": "^4.1.1", 23 | "postcss-csso": "^2.0.0", 24 | "postcss-scss": "^1.0.2", 25 | "pump": "^1.0.2" 26 | }, 27 | "scripts": { 28 | "install": "composer install" 29 | }, 30 | "config": { 31 | "unsafe-perm": true 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/PASSYpw/PASSY.git" 36 | }, 37 | "keywords": [ 38 | "PASSY", 39 | "password", 40 | "manager" 41 | ], 42 | "author": { 43 | "name": "Scrumplex", 44 | "email": "contact@scrumplex.net", 45 | "url": "https://scrumplex.net" 46 | }, 47 | "contributors": [ 48 | { 49 | "name": "Liz3", 50 | "email": "info@liz3.de", 51 | "url": "https://liz3.de" 52 | } 53 | ], 54 | "license": "GPL-3.0", 55 | "bugs": { 56 | "url": "https://github.com/PASSYpw/PASSY/issues" 57 | }, 58 | "homepage": "https://github.com/PASSYpw/PASSY#readme" 59 | } 60 | -------------------------------------------------------------------------------- /page/page_archived_password_list.inc.php: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /page/page_login.inc.php: -------------------------------------------------------------------------------- 1 | 4 | 73 | -------------------------------------------------------------------------------- /page/page_login_history.inc.php: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /page/page_password_list.inc.php: -------------------------------------------------------------------------------- 1 | 89 | 90 | 131 | -------------------------------------------------------------------------------- /page/page_register.inc.php: -------------------------------------------------------------------------------- 1 | 4 | 49 | 50 | -------------------------------------------------------------------------------- /page/page_user_settings.inc.php: -------------------------------------------------------------------------------- 1 | 131 | 132 | 253 | 254 | -------------------------------------------------------------------------------- /src/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all 2 | order deny,allow 3 | -------------------------------------------------------------------------------- /src/PASSY/Database.php: -------------------------------------------------------------------------------- 1 | 13 | * @package PASSY 14 | */ 15 | class Database 16 | { 17 | 18 | /** 19 | * @var array 20 | */ 21 | private $mysqlConfig; 22 | /** 23 | * @var mysqli 24 | */ 25 | private $mysql; 26 | 27 | /** 28 | * Database constructor. 29 | * @author Sefa Eyeoglu 30 | * @param $mysqlConfig 31 | */ 32 | function __construct($mysqlConfig) 33 | { 34 | PASSY::$db = $this; 35 | $this->mysqlConfig = $mysqlConfig; 36 | } 37 | 38 | /** 39 | * Connects to mysql server with mysqli. 40 | * @author Sefa Eyeoglu 41 | * @throws Exception connect error 42 | */ 43 | function connect() 44 | { 45 | mysqli_report(MYSQLI_REPORT_STRICT); 46 | 47 | $this->mysql = new mysqli($this->mysqlConfig['host'], $this->mysqlConfig['user'], $this->mysqlConfig['password'], $this->mysqlConfig['db']); 48 | if ($this->mysql->connect_error) 49 | throw new Exception($this->mysql->connect_error); 50 | 51 | $this->mysql->set_charset('utf8'); 52 | 53 | if (!($this->mysql->query("CREATE TABLE IF NOT EXISTS `users` (`USERNAME` VARCHAR(20) NOT NULL, `USERID` VARCHAR(18) NOT NULL, `PASSWORD` VARCHAR(128) NOT NULL, `SALT` VARCHAR(128) NOT NULL) ENGINE = InnoDB DEFAULT CHARSET = utf8;"))) 54 | throw new Exception("Could not create table \"users\""); 55 | 56 | if (!($this->mysql->query("CREATE TABLE IF NOT EXISTS `passwords` (`ID` VARCHAR(18) NOT NULL, `USERID` VARCHAR(18) NOT NULL, `USERNAME` VARCHAR(64), `PASSWORD` VARCHAR(512) NOT NULL, `DESCRIPTION` VARCHAR(32) NOT NULL, `DATE` INT(11) NOT NULL, `ARCHIVED_DATE` INT(11)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"))) 57 | throw new Exception("Could not create table \"passwords\""); 58 | 59 | if (!($this->mysql->query("CREATE TABLE IF NOT EXISTS `iplog` (`USERID` VARCHAR(18) NOT NULL, `IP` VARCHAR(48) NOT NULL, `USERAGENT` VARCHAR(128) NOT NULL, `DATE` INT(11) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;"))) 60 | throw new Exception("Could not create table \"iplog\""); 61 | 62 | if (!($this->mysql->query("CREATE TABLE IF NOT EXISTS `metadata` (`KEY` VARCHAR(64) NOT NULL, `VALUE` VARCHAR(64)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"))) 63 | throw new Exception("Could not create table \"metadata\""); 64 | 65 | if (!($this->mysql->query("CREATE TABLE IF NOT EXISTS `twofactor` (`USERID` VARCHAR(18) NOT NULL, `SECRETKEY` TEXT, `DATE` INT(11) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;"))) 66 | throw new Exception("Could not create table \"metadata\""); 67 | 68 | if (!($this->containsMetadata("db_version"))) { 69 | $this->addMetadata("db_version", PASSY_BUILD); 70 | } 71 | } 72 | 73 | /** 74 | * This method simply help to shorted mysql commands, on success an array is returned containing: 75 | * true 76 | * if available the results object 77 | * boolean if the result row count is not null e.g 0 = false not 0 = true 78 | * the prepared error 79 | * and the mysql connection 80 | * 81 | * on a error also an array is returned with 82 | * false 83 | * the prepared error 84 | * the mysql connection error 85 | * 86 | * @author Liz3 87 | * @param string $statement 88 | * @param string $keys 89 | * @param array ...$args 90 | * @return array 91 | */ 92 | function easy_exec($statement, $keys, ...$args) 93 | { 94 | $arr = array(); 95 | array_push($arr, $keys); 96 | foreach ($args as $arg) { 97 | array_push($arr, $arg); 98 | } 99 | $tmp = array(); 100 | foreach ($arr as $key => $value) $tmp[$key] = &$arr[$key]; 101 | $prepared = $this->mysql->prepare($statement); 102 | call_user_func_array(array($prepared, 'bind_param'), $tmp); 103 | $success = $prepared->execute(); 104 | if (!$success) 105 | return array(false, $prepared->error, $this->mysql->error); 106 | 107 | $arr = array(true); 108 | $result = $prepared->get_result(); 109 | array_push($arr, $result); 110 | array_push($arr, is_object($result) ? $result->num_rows : 0); 111 | array_push($arr, is_object($prepared) ? $prepared->insert_id : 0); 112 | array_push($arr, $prepared->error); 113 | array_push($arr, $this->mysql->error); 114 | 115 | $prepared->close(); 116 | return $arr; 117 | } 118 | 119 | /** 120 | * Getter function for current mysqli instance. Connects to database if not initialized. 121 | * @author Sefa Eyeoglu 122 | * @return mysqli current mysqli instance. 123 | */ 124 | function getInstance() 125 | { 126 | if (!isset($this->mysql)) 127 | $this->connect(); 128 | 129 | return $this->mysql; 130 | } 131 | 132 | function upgrade() 133 | { 134 | return; //TODO 135 | $this->getInstance(); 136 | $db_version = $this->getMetadata("db_version"); 137 | if ($db_version < PASSY_BUILD) { 138 | switch ($db_version) { 139 | case 202: // Upgrade from 2.0.2 140 | 141 | break; 142 | } 143 | $this->updateMetadata("db_version", PASSY_BUILD); 144 | } 145 | } 146 | 147 | /** 148 | * @param $key 149 | * @return string|bool Value. If key does not exist it returns false. 150 | */ 151 | function getMetadata($key) 152 | { 153 | $this->getInstance(); 154 | $ps = $this->mysql->prepare("SELECT `VALUE` FROM `metadata` WHERE `KEY` = (?)"); 155 | $ps->bind_param("s", $key); 156 | $succeeded = $ps->execute(); 157 | $result = $ps->get_result(); 158 | $ps->close(); 159 | if ($succeeded && $result->num_rows > 0) { 160 | $row = $result->fetch_assoc(); 161 | return $row["VALUE"]; 162 | } 163 | return false; 164 | } 165 | 166 | /** 167 | * Checks if database contains metadata with key. 168 | * @param $key 169 | * @return bool if exists or not 170 | */ 171 | function containsMetadata($key) 172 | { 173 | return $this->getMetadata($key) !== false; 174 | } 175 | 176 | /** 177 | * @param $key 178 | * @param $value 179 | * @return boolean if succeeded or not 180 | */ 181 | function addMetadata($key, $value) 182 | { 183 | $this->getInstance(); 184 | $ps = $this->mysql->prepare("INSERT INTO `metadata` (`KEY`, `VALUE`) VALUES (?, ?)"); 185 | $ps->bind_param("ss", $key, $value); 186 | $succeeded = $ps->execute(); 187 | $ps->close(); 188 | return $succeeded; 189 | } 190 | 191 | /** 192 | * @param $key 193 | * @param $value 194 | * @return boolean if succeeded or not 195 | */ 196 | function updateMetadata($key, $value) 197 | { 198 | $this->getInstance(); 199 | $ps = $this->mysql->prepare("UPDATE `metadata` SET `VALUE` = (?) WHERE `KEY` = (?)"); 200 | $ps->bind_param("ss", $value, $key); 201 | $succeeded = $ps->execute(); 202 | $ps->close(); 203 | return $succeeded; 204 | } 205 | 206 | } -------------------------------------------------------------------------------- /src/PASSY/Format.php: -------------------------------------------------------------------------------- 1 | 8 | * @package PASSY 9 | */ 10 | class Format 11 | { 12 | 13 | /** 14 | * Returns human readable time formats. 15 | * @author Sefa Eyeoglu 16 | * @param $time int timestamp to format 17 | * @return string formatted string 18 | */ 19 | static function formatTime($time) 20 | { 21 | if ($time == null || $time == 0) 22 | return "never"; 23 | 24 | $now = time(); 25 | $seconds = $now - $time; 26 | $minutes = floor($seconds / 60); 27 | $hours = floor($minutes / 60); 28 | $days = floor($hours / 24); 29 | $weeks = floor($days / 7); 30 | $months = floor($weeks / 4.3); 31 | $years = floor($months / 12); 32 | 33 | if ($seconds < 60) { 34 | if ($seconds < 20) 35 | return "just now"; 36 | if ($seconds < 40) 37 | return "recently"; 38 | return Format::writtenNumber($seconds) . " seconds ago"; 39 | } 40 | if ($minutes < 60) { 41 | if ($minutes == 1) 42 | return "one minute ago"; 43 | return Format::writtenNumber($minutes) . " minutes ago"; 44 | } 45 | if ($hours < 24) { 46 | if ($hours == 1) 47 | return "one hour ago"; 48 | return Format::writtenNumber($hours) . " hours ago"; 49 | } 50 | if ($days < 7) { 51 | if ($days == 1) 52 | return "one day ago"; 53 | return Format::writtenNumber($days) . " days ago"; 54 | } 55 | if ($weeks < 4.3) { 56 | if ($weeks == 1) 57 | return "one week ago"; 58 | return Format::writtenNumber($weeks) . " weeks ago"; 59 | } 60 | if ($months < 12) { 61 | if ($months == 1) 62 | return "one month ago"; 63 | return Format::writtenNumber($months) . " months ago"; 64 | } 65 | if ($years == 1) 66 | return "one year ago"; 67 | return Format::writtenNumber($years) . " years ago"; 68 | } 69 | 70 | /** 71 | * Returns a string of the given number. 72 | * Numbers from 0 to 12 are written (e.g. 1: one, 2: two...) 73 | * @author Sefa Eyeoglu 74 | * @param $number int number to format 75 | * @return string|int written number (0:12) or $number 76 | */ 77 | static function writtenNumber($number) 78 | { 79 | switch ($number) { 80 | case 0: 81 | return "zero"; 82 | case 1: 83 | return "one"; 84 | case 2: 85 | return "two"; 86 | case 3: 87 | return "three"; 88 | case 4: 89 | return "four"; 90 | case 5: 91 | return "five"; 92 | case 6: 93 | return "six"; 94 | case 7: 95 | return "seven"; 96 | case 8: 97 | return "eight"; 98 | case 9: 99 | return "nine"; 100 | case 10: 101 | return "ten"; 102 | case 11: 103 | return "eleven"; 104 | case 12: 105 | return "twelve"; 106 | } 107 | return $number; 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /src/PASSY/IPLog.php: -------------------------------------------------------------------------------- 1 | 8 | * @package PASSY 9 | */ 10 | class IPLog 11 | { 12 | 13 | /** 14 | * IPLog constructor. 15 | * @author Sefa Eyeoglu 16 | */ 17 | function __construct() 18 | { 19 | PASSY::$ipLog = $this; 20 | } 21 | 22 | /** 23 | * Logs ip with given parameters in the database. 24 | * @author Sefa Eyeoglu 25 | * @param $ip 26 | * @param $userAgent 27 | * @param $userId 28 | * @return Response 29 | * @see \PASSY\Response 30 | */ 31 | function _logIP($ip, $userAgent, $userId) 32 | { 33 | $now = time(); 34 | 35 | $mysql = PASSY::$db->getInstance(); 36 | 37 | $ps = $mysql->prepare("INSERT INTO `iplog` (`USERID`, `IP`, `USERAGENT`, `DATE`) VALUES (?, ?, ?, ?)"); 38 | $ps->bind_param("ssss", $userId, $ip, $userAgent, $now); 39 | $succeeded = $ps->execute(); 40 | $ps->close(); 41 | if ($succeeded) 42 | return new Response(true, null); 43 | return new Response(false, "database_error"); 44 | } 45 | 46 | /** 47 | * Queries all entries for iplog for specific user 48 | * @author Sefa Eyeoglu 49 | * @param $userId 50 | * @return Response 51 | * @see \PASSY\Response 52 | */ 53 | function _queryAll($userId) 54 | { 55 | $mysql = PASSY::$db->getInstance(); 56 | 57 | $ps = $mysql->prepare("SELECT * FROM `iplog` WHERE `USERID` = (?) ORDER BY `DATE` DESC"); 58 | $ps->bind_param("s", $userId); 59 | $succeeded = $ps->execute(); 60 | $result = $ps->get_result(); 61 | $ps->close(); 62 | if ($succeeded) { 63 | $data = array(); 64 | if ($result->num_rows > 0) { 65 | while ($row = $result->fetch_assoc()) { 66 | $entry = array( 67 | "ip" => $row["IP"], 68 | "user_agent" => $row["USERAGENT"], 69 | "date" => array( 70 | "timestamp" => $row["DATE"], 71 | "pretty" => Format::formatTime($row["DATE"]) 72 | ) 73 | ); 74 | array_push($data, $entry); 75 | } 76 | } 77 | return new Response(true, $data); 78 | } 79 | return new Response(false, "database_error"); 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /src/PASSY/PASSY.php: -------------------------------------------------------------------------------- 1 | 11 | * @package PASSY 12 | */ 13 | class PASSY 14 | { 15 | /** 16 | * @var Database 17 | */ 18 | public static $db; 19 | 20 | /** 21 | * @var IPLog 22 | */ 23 | public static $ipLog; 24 | 25 | /** 26 | * @var Passwords 27 | */ 28 | public static $passwords; 29 | 30 | /** 31 | * @var Tasks 32 | */ 33 | public static $tasks; 34 | 35 | /** 36 | * @var TwoFactor 37 | */ 38 | public static $twoFactor; 39 | 40 | /** 41 | * @var UserManager 42 | */ 43 | public static $userManager; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/PASSY/Passwords.php: -------------------------------------------------------------------------------- 1 | 13 | * @author Liz3(Yann HN) 14 | * @package PASSY 15 | */ 16 | class Passwords 17 | { 18 | 19 | /** 20 | * Passwords constructor. 21 | * @author Sefa Eyeoglu 22 | */ 23 | function __construct() 24 | { 25 | PASSY::$passwords = $this; 26 | } 27 | 28 | /** 29 | * Creates a password entry in the database with given parameters. 30 | * @author Sefa Eyeoglu 31 | * @param $username 32 | * @param $password 33 | * @param $description 34 | * @param $userId 35 | * @param $masterPassword 36 | * @return Response 37 | * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException 38 | */ 39 | function _create($username, $password, $description, $userId, $masterPassword) 40 | { 41 | $passwordId = uniqid("pass_"); 42 | $encryptedPassword = Crypto::encryptWithPassword($password, $masterPassword); 43 | $date = time(); 44 | $archivedDate = null; 45 | 46 | $mysql = PASSY::$db->getInstance(); 47 | $ps = $mysql->prepare("INSERT INTO `passwords` (`ID`, `USERID`, `USERNAME`, `PASSWORD`, `DESCRIPTION`, `DATE`, `ARCHIVED_DATE`) VALUES (?, ?, ?, ?, ?, ?, ?)"); 48 | $ps->bind_param("sssssii", $passwordId, $userId, $username, $encryptedPassword, $description, $date, $archivedDate); 49 | $succeeded = $ps->execute(); 50 | $ps->close(); 51 | $response = new Response($succeeded, $succeeded ? $passwordId : "database_error"); 52 | return $response; 53 | } 54 | 55 | /** 56 | * Updates given password's data in the database. 57 | * @author Sefa Eyeoglu 58 | * @param $passwordId 59 | * @param $username 60 | * @param $password 61 | * @param $description 62 | * @param $masterPassword 63 | * @return Response 64 | * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException 65 | */ 66 | function _edit($passwordId, $username, $password, $description, $masterPassword) 67 | { 68 | $encryptedPassword = Crypto::encryptWithPassword($password, $masterPassword); 69 | 70 | $mysql = PASSY::$db->getInstance(); 71 | $ps = $mysql->prepare("UPDATE `passwords` SET `PASSWORD` = (?), `USERNAME` = (?), `DESCRIPTION` = (?) WHERE `ID` = (?)"); 72 | $ps->bind_param("ssss", $encryptedPassword, $username, $description, $passwordId); 73 | $succeeded = $ps->execute(); 74 | $ps->close(); 75 | $response = new Response($succeeded, $succeeded ? "" : "database_error"); 76 | return $response; 77 | } 78 | 79 | /** 80 | * Imports passwords, which will be encrypted with $masterPassword and assigned to $userId. 81 | * @author Liz3(Yann HN) 82 | * @param string $data 83 | * @param string $userId 84 | * @param string $masterPassword 85 | * @param bool $withPassword 86 | * @param string $importPassword 87 | * @param string $type 88 | * @return null|Response 89 | * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException 90 | * @throws \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 91 | */ 92 | function _import($data, $userId, $masterPassword, $withPassword, $importPassword, $type = "passy") 93 | { 94 | $count = 0; 95 | $failed = 0; 96 | if ($type == "passy") { 97 | $arr = json_decode($data, true); 98 | $data = $withPassword ? json_decode(Crypto::decryptWithPassword($arr["data"], $importPassword), true) : $arr["data"]; 99 | foreach ($data as $item) { 100 | 101 | if ($item["pass"] == null) { 102 | $failed++; 103 | continue; 104 | } 105 | $pass = $item["pass"]; 106 | $this->_create($item["username"], $pass, $item["description"], $userId, $masterPassword); 107 | $count++; 108 | } 109 | } else if ($type == "CSV") { 110 | 111 | $csv = Reader::createFromString($withPassword ? Crypto::decryptWithPassword($data, $importPassword) : $data); 112 | foreach ($csv->getIterator() as $item) { 113 | 114 | if (count($item) < 4) { 115 | $failed++; 116 | } 117 | $username = $item[2]; 118 | $password = $item[3]; 119 | $description = $item[0]; 120 | $this->_create($username, $password, $description, $userId, $masterPassword); 121 | $count++; 122 | } 123 | } else { 124 | return null; 125 | } 126 | return new Response(true, array( 127 | "imported" => $count, 128 | "failed" => $failed 129 | )); 130 | } 131 | 132 | /** 133 | * Export all passwords from $userId, which will be decrypted with $masterPassword. 134 | * @author Liz3(Yann HN) 135 | * @param string $userId 136 | * @param string $masterPassword 137 | * @param bool $withPassword 138 | * @param string $exportPassword 139 | * @return Response 140 | * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException 141 | * @throws \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 142 | */ 143 | function _exportAll($userId, $masterPassword, $withPassword, $exportPassword) 144 | { 145 | $data = array(); 146 | $mysql = PASSY::$db->getInstance(); 147 | $query = "SELECT `USERNAME`, `PASSWORD`, `DESCRIPTION`, `DATE`, `ARCHIVED_DATE` FROM `passwords` WHERE `USERID` = (?)"; 148 | $ps = $mysql->prepare($query); 149 | $ps->bind_param("s", $userId); 150 | $succeeded = $ps->execute(); 151 | $result = $ps->get_result(); 152 | $ps->close(); 153 | if ($succeeded) { 154 | if ($result->num_rows > 0) { 155 | while ($row = $result->fetch_assoc()) { 156 | $decryptedPassword = Crypto::decryptWithPassword($row['PASSWORD'], $masterPassword); 157 | 158 | $entry = array( 159 | "username" => $row["USERNAME"], 160 | "description" => $row["DESCRIPTION"], 161 | "date" => $row["DATE"], 162 | "archived" => $row["ARCHIVED_DATE"] != null, 163 | "pass" => $decryptedPassword 164 | 165 | ); 166 | 167 | if ($entry["archived"]) 168 | $entry["date_archived"] = $row["ARCHIVED_DATE"];; 169 | 170 | array_push($data, $entry); 171 | } 172 | } 173 | return new Response(true, $withPassword ? Crypto::encryptWithPassword(json_encode($data), $exportPassword) : $data); 174 | } 175 | return new Response(false, "database_error"); 176 | } 177 | 178 | /** 179 | * Queries info about given password 180 | * @author Sefa Eyeoglu 181 | * @param $passwordId 182 | * @param mixed $masterPassword used to decrypt passwords. If null it won't decrypt. 183 | * @return Response 184 | * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException 185 | * @throws \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 186 | */ 187 | function _query($passwordId, $masterPassword = null) 188 | { 189 | 190 | $mysql = PASSY::$db->getInstance(); 191 | $query = "SELECT `ID`, `USERID`, `USERNAME`, `DESCRIPTION`, `DATE`, `ARCHIVED_DATE` FROM `passwords` WHERE `ID` = (?)"; 192 | if (isset($masterPassword)) 193 | $query = "SELECT `ID`, `USERID`, `USERNAME`, `PASSWORD`, `DESCRIPTION`, `DATE`, `ARCHIVED_DATE` FROM `passwords` WHERE `ID` = (?)"; 194 | $ps = $mysql->prepare($query); 195 | $ps->bind_param("s", $passwordId); 196 | $succeeded = $ps->execute(); 197 | $result = $ps->get_result(); 198 | $ps->close(); 199 | if ($succeeded && $result->num_rows > 0) { 200 | $row = $result->fetch_assoc(); 201 | 202 | $username = $row["USERNAME"]; 203 | if (strlen($username) == 0) 204 | $username = null; 205 | 206 | $description = $row["DESCRIPTION"]; 207 | if (strlen($description) == 0) 208 | $description = null; 209 | 210 | $entry = array( 211 | "user_id" => $row["USERID"], 212 | "password" => array( 213 | "raw" => null, 214 | "safe" => null 215 | ), 216 | "password_id" => $row["ID"], 217 | "username" => array( 218 | "raw" => $username, 219 | "safe" => Util::filterStrings($username) 220 | ), 221 | "description" => array( 222 | "raw" => $description, 223 | "safe" => Util::filterStrings($description) 224 | ), 225 | "date_added" => array( 226 | "timestamp" => $row["DATE"], 227 | "pretty" => Format::formatTime($row["DATE"]) 228 | ), 229 | "archived" => $row["ARCHIVED_DATE"] !== null, 230 | "date_archived" => array( 231 | "timestamp" => $row["DATE"], 232 | "pretty" => Format::formatTime($row["DATE"]) 233 | ), 234 | ); 235 | 236 | if (isset($masterPassword)) { 237 | $decryptedPassword = Crypto::decryptWithPassword($row['PASSWORD'], $masterPassword); 238 | $entry["password"]["raw"] = $decryptedPassword; 239 | $entry["password"]["safe"] = Util::filterStrings($decryptedPassword); 240 | } 241 | return new Response(true, $entry); 242 | } 243 | return new Response(false, "database_error"); 244 | } 245 | 246 | /** 247 | * Queries all passwords from user. 248 | * @param $userId 249 | * @param mixed $masterPassword used to decrypt passwords. If null it won't decrypt. 250 | * @return Response 251 | * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException 252 | * @throws \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 253 | */ 254 | function _queryAll($userId, $masterPassword = null) 255 | { 256 | 257 | $mysql = PASSY::$db->getInstance(); 258 | $query = "SELECT `ID`, `USERID`, `USERNAME`, `DESCRIPTION`, `DATE`, `ARCHIVED_DATE` FROM `passwords` WHERE `USERID` = (?)"; 259 | if (isset($masterPassword)) 260 | $query = "SELECT `ID`, `USERID`, `USERNAME`, `PASSWORD`, `DESCRIPTION`, `DATE`, `ARCHIVED_DATE` FROM `passwords` WHERE `USERID` = (?)"; 261 | $ps = $mysql->prepare($query); 262 | $ps->bind_param("s", $userId); 263 | $succeeded = $ps->execute(); 264 | $result = $ps->get_result(); 265 | $ps->close(); 266 | if ($succeeded) { 267 | $data = array(); 268 | if ($result->num_rows > 0) { 269 | while ($row = $result->fetch_assoc()) { 270 | 271 | $username = $row["USERNAME"]; 272 | if (strlen($username) == 0) 273 | $username = null; 274 | 275 | $description = $row["DESCRIPTION"]; 276 | if (strlen($description) == 0) 277 | $description = null; 278 | 279 | $entry = array( 280 | "user_id" => $row["USERID"], 281 | "password" => array( 282 | "raw" => null, 283 | "safe" => null 284 | ), 285 | "password_id" => $row["ID"], 286 | "username" => array( 287 | "raw" => $username, 288 | "safe" => Util::filterStrings($username) 289 | ), 290 | "description" => array( 291 | "raw" => $description, 292 | "safe" => Util::filterStrings($description) 293 | ), 294 | "date_added" => array( 295 | "timestamp" => $row["DATE"], 296 | "pretty" => Format::formatTime($row["DATE"]) 297 | ), 298 | "archived" => $row["ARCHIVED_DATE"] !== null, 299 | "date_archived" => array( 300 | "timestamp" => $row["ARCHIVED_DATE"], 301 | "pretty" => Format::formatTime($row["ARCHIVED_DATE"]) 302 | ), 303 | ); 304 | 305 | if (isset($masterPassword)) { 306 | $decryptedPassword = Crypto::decryptWithPassword($row['PASSWORD'], $masterPassword); 307 | $entry["password"]["raw"] = $decryptedPassword; 308 | $entry["password"]["safe"] = Util::filterStrings($decryptedPassword); 309 | } 310 | array_push($data, $entry); 311 | } 312 | } 313 | return new Response(true, $data); 314 | } 315 | return new Response(false, "database_error"); 316 | } 317 | 318 | /** 319 | * Undocumented 320 | * @param $userId 321 | * @param $oldMasterPassword 322 | * @param $newMasterPassword 323 | * @return Response 324 | * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException 325 | * @throws \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 326 | */ 327 | function _reencryptPasswords($userId, $oldMasterPassword, $newMasterPassword) 328 | { 329 | $mysql = PASSY::$db->getInstance(); 330 | $ps = $mysql->prepare("SELECT `ID`, `PASSWORD` FROM `passwords` WHERE `USERID` = (?)"); 331 | $ps->bind_param("s", $userId); 332 | $succeeded = $ps->execute(); 333 | $result = $ps->get_result(); 334 | $ps->close(); 335 | if ($succeeded) { 336 | if ($result->num_rows > 0) { 337 | $ps = $mysql->prepare("UPDATE `passwords` SET `PASSWORD` = (?) WHERE `ID` = (?)"); 338 | while ($row = $result->fetch_assoc()) { 339 | $passwordId = $row["ID"]; 340 | $decryptedPassword = Crypto::decryptWithPassword($row['PASSWORD'], $oldMasterPassword); 341 | $reencryptedPassword = Crypto::encryptWithPassword($decryptedPassword, $newMasterPassword); 342 | $ps->bind_param("ss", $reencryptedPassword, $passwordId); 343 | $succeeded = $ps->execute(); 344 | if (!$succeeded) 345 | return new Response(false, "database_error"); 346 | } 347 | $ps->close(); 348 | } 349 | return new Response(true, null); 350 | } 351 | return new Response(false, "database_error"); 352 | } 353 | 354 | /** 355 | * Adds the flag archived to a password 356 | * @param $passwordId 357 | * @return Response 358 | */ 359 | function _archive($passwordId) 360 | { 361 | $archivedDate = time(); 362 | 363 | $mysql = PASSY::$db->getInstance(); 364 | $ps = $mysql->prepare("UPDATE `passwords` SET `ARCHIVED_DATE` = (?) WHERE `ID` = (?)"); 365 | $ps->bind_param("is", $archivedDate, $passwordId); 366 | $succeeded = $ps->execute(); 367 | $ps->close(); 368 | if ($succeeded) 369 | return new Response(true, null); 370 | return new Response(false, "database_error"); 371 | } 372 | 373 | /** 374 | * Removes the flag archived to a password 375 | * @param $passwordId 376 | * @return Response 377 | */ 378 | function _restore($passwordId) 379 | { 380 | $archivedDate = null; 381 | 382 | $mysql = PASSY::$db->getInstance(); 383 | $ps = $mysql->prepare("UPDATE `passwords` SET `ARCHIVED_DATE` = (?) WHERE `ID` = (?)"); 384 | $ps->bind_param("is", $archivedDate, $passwordId); 385 | $succeeded = $ps->execute(); 386 | $ps->close(); 387 | if ($succeeded) 388 | return new Response(true, null); 389 | return new Response(false, "database_error"); 390 | } 391 | 392 | /** 393 | * Permanently deletes password 394 | * @param $passwordId 395 | * @return Response 396 | */ 397 | function _delete($passwordId) 398 | { 399 | 400 | $mysql = PASSY::$db->getInstance(); 401 | $ps = $mysql->prepare("DELETE FROM `passwords` WHERE `ID` = (?)"); 402 | $ps->bind_param("s", $passwordId); 403 | $succeeded = $ps->execute(); 404 | $ps->close(); 405 | if ($succeeded) 406 | return new Response(true, null); 407 | return new Response(false, "database_error"); 408 | } 409 | 410 | 411 | } -------------------------------------------------------------------------------- /src/PASSY/Response.php: -------------------------------------------------------------------------------- 1 | 9 | * @package PASSY 10 | */ 11 | class Response 12 | { 13 | 14 | /** 15 | * @var bool 16 | */ 17 | private $success; 18 | /** 19 | * @var mixed 20 | */ 21 | private $data; 22 | 23 | /** 24 | * Response constructor. 25 | * @param boolean $success Defines if request was successful or not 26 | * @param mixed $data The data to be returned. It should be array of string. If $success is false the data will be returned under "msg" field of the JSON. 27 | */ 28 | function __construct($success, $data) 29 | { 30 | $this->success = $success; 31 | $this->data = $data; 32 | } 33 | 34 | /** 35 | * @return string json encoded string of response 36 | */ 37 | function getJSONResponse() 38 | { 39 | return json_encode(array( 40 | "success" => $this->success, 41 | "timestamp" => time(), 42 | "msg" => $this->success ? "success" : $this->data, 43 | "data" => !$this->success ? null : $this->data, 44 | )); 45 | } 46 | 47 | /** 48 | * @return boolean 49 | */ 50 | public function wasSuccess() 51 | { 52 | return $this->success; 53 | } 54 | 55 | /** 56 | * @return mixed 57 | */ 58 | public function getData() 59 | { 60 | return $this->data; 61 | } 62 | 63 | 64 | } -------------------------------------------------------------------------------- /src/PASSY/Tasks.php: -------------------------------------------------------------------------------- 1 | 8 | * @package PASSY 9 | */ 10 | class Tasks 11 | { 12 | 13 | function __construct() 14 | { 15 | PASSY::$tasks = $this; 16 | } 17 | 18 | function run() 19 | { 20 | $this->clearArchivedPasswords(); 21 | $this->clearLoginHistory(); 22 | } 23 | 24 | private function clearArchivedPasswords() 25 | { 26 | $maxAge = 2 * 7 * 24 * 60 * 60; // two weeks in seconds 27 | $deleteOlderThan = time() - $maxAge; // Now - 2 weeks 28 | 29 | $mysql = PASSY::$db->getInstance(); 30 | 31 | $ps = $mysql->prepare("DELETE FROM `passwords` WHERE ARCHIVED_DATE <= (?)"); 32 | $ps->bind_param("i", $deleteOlderThan); 33 | return $ps->execute(); 34 | } 35 | 36 | private function clearLoginHistory() 37 | { 38 | $maxAge = 2 * 7 * 24 * 60 * 60; // two weeks in seconds 39 | $deleteOlderThan = time() - $maxAge; // Now - 2 weeks 40 | 41 | $mysql = PASSY::$db->getInstance(); 42 | 43 | $ps = $mysql->prepare("DELETE FROM `iplog` WHERE iplog.DATE <= (?)"); 44 | $ps->bind_param("i", $deleteOlderThan); 45 | return $ps->execute(); 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /src/PASSY/TwoFactor.php: -------------------------------------------------------------------------------- 1 | 13 | * @package PASSY 14 | */ 15 | class TwoFactor 16 | { 17 | 18 | public static $KEYLENGTH = 32; 19 | 20 | private $google2fa; 21 | 22 | function __construct() 23 | { 24 | PASSY::$twoFactor = $this; 25 | $this->google2fa = new Google2FA(); 26 | } 27 | 28 | /** 29 | * Generates secret key for Two-Factor-Authentication. This function does not save anything. 30 | * @param $displayName 31 | * @return Response 32 | */ 33 | function _generateSecretKey($displayName) 34 | { 35 | $privateKey = $this->google2fa->generateSecretKey(TwoFactor::$KEYLENGTH); 36 | $qrUrl = $this->google2fa->getQRCodeGoogleUrl('PASSY', $displayName, $privateKey); 37 | return new Response(true, array( 38 | "privateKey" => $privateKey, 39 | "qrCodeUrl" => $qrUrl 40 | )); 41 | } 42 | 43 | /** 44 | * Enables Two-Factor-Authentication for the specified userId 45 | * @param $userId 46 | * @param $masterPassword 47 | * @param $secretKey 48 | * @param $enteredCode 49 | * @return Response 50 | * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException 51 | */ 52 | function _enable2FA($userId, $masterPassword, $secretKey, $enteredCode) 53 | { 54 | if (!$this->google2fa->verifyKey($secretKey, $enteredCode)) { 55 | return new Response(false, "invalid_code"); 56 | } 57 | $encryptedSecretKey = Crypto::encryptWithPassword($secretKey, $masterPassword); 58 | $now = time(); 59 | $mysql = PASSY::$db->getInstance(); 60 | $ps = $mysql->prepare("INSERT INTO `twofactor` (`USERID`, `SECRETKEY`, `DATE`) VALUES (?, ?, ?)"); 61 | $ps->bind_param("ssi", $userId, $encryptedSecretKey, $now); 62 | $succeeded = $ps->execute(); 63 | $ps->close(); 64 | return new Response($succeeded, ($succeeded ? array() : "database_error")); 65 | } 66 | 67 | function _disable2FA($userId) 68 | { 69 | $mysql = PASSY::$db->getInstance(); 70 | $ps = $mysql->prepare("DELETE FROM `twofactor` WHERE `USERID` = (?)"); 71 | $ps->bind_param("s", $userId); 72 | $succeeded = $ps->execute(); 73 | $ps->close(); 74 | return new Response($succeeded, ($succeeded ? array() : "database_error")); 75 | } 76 | 77 | function _checkCode($userId, $masterPassword, $enteredCode) 78 | { 79 | $mysql = PASSY::$db->getInstance(); 80 | $ps = $mysql->prepare("SELECT `SECRETKEY` FROM `twofactor` WHERE `USERID` = (?)"); 81 | $ps->bind_param("s", $userId); 82 | $succeeded = $ps->execute(); 83 | $result = $ps->get_result(); 84 | $ps->close(); 85 | if ($succeeded) { 86 | $row = $result->fetch_assoc(); 87 | $encryptedSecretKey = $row["SECRETKEY"]; 88 | $secretKey = Crypto::decryptWithPassword($encryptedSecretKey, $masterPassword); 89 | 90 | if ($this->google2fa->verifyKey($secretKey, $enteredCode)) { 91 | return new Response(true, array()); 92 | } 93 | return new Response(false, "invalid_code"); 94 | } 95 | return new Response(false, "database_error"); 96 | } 97 | 98 | function isEnabled($userId) 99 | { 100 | $mysql = PASSY::$db->getInstance(); 101 | $ps = $mysql->prepare("SELECT `DATE` FROM `twofactor` WHERE `USERID` = (?)"); 102 | $ps->bind_param("s", $userId); 103 | $succeeded = $ps->execute(); 104 | $result = $ps->get_result(); 105 | $ps->close(); 106 | if ($succeeded) { 107 | $enabled = $result->num_rows > 0; 108 | $date = null; 109 | return $enabled; 110 | } 111 | return false; 112 | } 113 | 114 | function checkPrivateKey($userId, $masterPassword, $privateKey) 115 | { 116 | $mysql = PASSY::$db->getInstance(); 117 | $ps = $mysql->prepare("SELECT `SECRETKEY` FROM `twofactor` WHERE `USERID` = (?)"); 118 | $ps->bind_param("s", $userId); 119 | $succeeded = $ps->execute(); 120 | $result = $ps->get_result(); 121 | $ps->close(); 122 | if ($succeeded) { 123 | $row = $result->fetch_assoc(); 124 | $encryptedSecretKey = $row["SECRETKEY"]; 125 | $secretKey = Crypto::decryptWithPassword($encryptedSecretKey, $masterPassword); 126 | return $secretKey == $privateKey; 127 | } 128 | return false; 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /src/PASSY/UserManager.php: -------------------------------------------------------------------------------- 1 | 8 | * @package PASSY 9 | */ 10 | class UserManager 11 | { 12 | 13 | /** 14 | * UserManager constructor. 15 | * @param boolean $forceSSL if true, session will only be allowed over HTTPS 16 | */ 17 | function __construct($forceSSL) 18 | { 19 | PASSY::$userManager = $this; 20 | ini_set('session.cookie_lifetime', 60 * 60 * 24 * 90); // 90 days 21 | ini_set('session.gc_maxlifetime', 60 * 60 * 24 * 90); // 90 days 22 | if ($forceSSL) 23 | ini_set("session.cookie_secure", true); 24 | 25 | session_start(); 26 | } 27 | 28 | /** 29 | * Tracks user activity. Used for logging user out after 300s. 30 | */ 31 | function trackActivity() 32 | { 33 | $_SESSION["last_activity"] = time(); 34 | } 35 | 36 | /** 37 | * If user is inactive too long he will be logged out 38 | */ 39 | function checkSessionExpiration() 40 | { 41 | if (!$this->isAuthenticated()) 42 | return; 43 | if ($this->getSessionExpirationTime() != 0 && (time() - $_SESSION["last_activity"]) >= $this->getSessionExpirationTime()) 44 | $this->_logout(); 45 | } 46 | 47 | /** 48 | * Checks user credentials. If credentials are correct it will create a valid session. 49 | * @param $username string 50 | * @param $password string 51 | * @return Response 52 | */ 53 | function _login($username, $password) 54 | { 55 | $mysql = PASSY::$db->getInstance(); 56 | $ps = $mysql->prepare("SELECT * FROM `users` WHERE `USERNAME` = (?)"); 57 | $ps->bind_param("s", $username); 58 | $succeeded = $ps->execute(); 59 | $result = $ps->get_result(); 60 | $ps->close(); 61 | if ($succeeded) { 62 | if ($result->num_rows > 0) { 63 | $row = $result->fetch_assoc(); 64 | $hashedPassword = hash("SHA512", $password . $row["SALT"]); 65 | if ($hashedPassword == $row['PASSWORD']) { 66 | // Save important data in session 67 | $_SESSION["username"] = $username; 68 | $_SESSION["master_password"] = $password; 69 | $_SESSION["userId"] = $row['USERID']; 70 | $_SESSION["ip"] = $_SERVER["REMOTE_ADDR"]; 71 | $_SESSION["session_expiration"] = 300; // 5 mins 72 | 73 | $this->trackActivity(); 74 | PASSY::$tasks->run(); // Run tasks every time, someone logs in successfully. 75 | return new Response(true, array()); 76 | } 77 | } 78 | return new Response(false, "invalid_credentials"); 79 | } 80 | return new Response(false, "database_error"); 81 | } 82 | 83 | /** 84 | * Creates a user with specified $username and $password. 85 | * @param $username string 86 | * @param $password string 87 | * @return Response 88 | */ 89 | function _register($username, $password) 90 | { 91 | $userId = uniqid("user_"); 92 | $salt = hash("SHA512", uniqid()); 93 | $hashedPassword = hash("SHA512", $password . $salt); 94 | 95 | $mysql = PASSY::$db->getInstance(); 96 | $ps = $mysql->prepare("SELECT * FROM `users` WHERE `USERNAME` = (?)"); 97 | $ps->bind_param("s", $username); 98 | $succeeded = $ps->execute(); 99 | $result = $ps->get_result(); 100 | $ps->close(); 101 | if ($succeeded) { 102 | if ($result->num_rows == 0) { 103 | $ps = $mysql->prepare("INSERT INTO `users` (`USERNAME`, `USERID`, `PASSWORD`, `SALT`) VALUES (?, ?, ?, ?)"); 104 | $ps->bind_param("ssss", $username, $userId, $hashedPassword, $salt); 105 | $succeeded = $ps->execute(); 106 | $ps->close(); 107 | if ($succeeded) 108 | return new Response(true, array()); 109 | return new Response(false, "database_error"); 110 | } 111 | return new Response(false, "username_exists"); 112 | } 113 | return new Response(false, "database_error"); 114 | } 115 | 116 | /** 117 | * Collects data, about the current status of the user's session. 118 | * 119 | * @return Response 120 | */ 121 | function _status() 122 | { 123 | if ($this->getSessionExpirationTime() != 0) 124 | $ttl = $this->getSessionExpirationTime() - (time() - $this->getLastActivity()); 125 | else 126 | $ttl = 0; 127 | 128 | $twoFactor = array(); 129 | 130 | $userId = $this->getUserID(); 131 | $mysql = PASSY::$db->getInstance(); 132 | $ps = $mysql->prepare("SELECT * FROM `twofactor` WHERE `USERID` = (?)"); 133 | $ps->bind_param("s", $userId); 134 | $succeeded = $ps->execute(); 135 | $result = $ps->get_result(); 136 | $ps->close(); 137 | if ($succeeded) { 138 | $enabled = $result->num_rows > 0; 139 | $date = null; 140 | if ($enabled) { 141 | $row = $result->fetch_assoc(); 142 | $date = $row["DATE"]; 143 | } 144 | $twoFactor = array( 145 | "enabled" => $enabled, 146 | "date" => $date 147 | ); 148 | } 149 | 150 | $response = new Response(true, array( 151 | "logged_in" => $this->isAuthenticated(), 152 | "last_activity" => $this->getLastActivity(), 153 | "ttl" => $ttl, 154 | "user_id" => $this->getUserID(), 155 | "two_factor" => $twoFactor 156 | )); 157 | return $response; 158 | } 159 | 160 | /** 161 | * Updates username of $userId 162 | * @param $userId 163 | * @param $newUsername 164 | * @return Response 165 | */ 166 | function _changeUsername($userId, $newUsername) 167 | { 168 | $mysql = PASSY::$db->getInstance(); 169 | $ps = $mysql->prepare("SELECT `USERID` FROM `users` WHERE `USERNAME` = (?)"); 170 | $ps->bind_param("s", $newUsername); 171 | $succeeded = $ps->execute(); 172 | $result = $ps->get_result(); 173 | $ps->close(); 174 | if ($succeeded) { 175 | if ($result->num_rows == 0) { 176 | $ps = $mysql->prepare("UPDATE `users` SET `USERNAME` = (?) WHERE `USERID` = (?)"); 177 | $ps->bind_param("ss", $newUsername, $userId); 178 | $succeeded = $ps->execute(); 179 | $ps->close(); 180 | if ($succeeded) 181 | return new Response(true, array()); 182 | return new Response(false, "database_error"); 183 | } 184 | return new Response(false, "username_exists"); 185 | } 186 | return new Response(false, "database_error"); 187 | } 188 | 189 | /** 190 | * Updates password of $userId. Re encrypts all passwords with new passwords. 191 | * @param $userId 192 | * @param $masterPassword 193 | * @param $newPassword 194 | * @return Response 195 | */ 196 | function _changePassword($userId, $masterPassword, $newPassword) 197 | { 198 | if (PASSY::$twoFactor->isEnabled($userId)) 199 | return new Response(false, "2fa_enabled"); 200 | 201 | $mysql = PASSY::$db->getInstance(); 202 | $ps = $mysql->prepare("SELECT `SALT` FROM `users` WHERE `USERID` = (?)"); 203 | $ps->bind_param("s", $userId); 204 | $succeeded = $ps->execute(); 205 | $result = $ps->get_result(); 206 | $ps->close(); 207 | if ($succeeded && $result->num_rows > 0) { 208 | $row = $result->fetch_assoc(); 209 | $salt = $row["SALT"]; 210 | $hashedPassword = hash("SHA512", $newPassword . $salt); 211 | 212 | $ps = $mysql->prepare("UPDATE `users` SET `PASSWORD` = (?) WHERE `USERID` = (?)"); 213 | $ps->bind_param("ss", $hashedPassword, $userId); 214 | $succeeded = $ps->execute(); 215 | if ($succeeded) 216 | return PASSY::$passwords->_reencryptPasswords($userId, $masterPassword, $newPassword); 217 | return new Response(true, array()); 218 | } 219 | return new Response(false, "database_error"); 220 | } 221 | 222 | /** 223 | * Destroys current session, if present. 224 | * @return Response 225 | */ 226 | function _logout() 227 | { 228 | if (session_status() != PHP_SESSION_NONE) 229 | session_destroy(); 230 | 231 | return new Response(true, array()); 232 | } 233 | 234 | /** 235 | * Checks if given $password is the same as password used to login and en/decrypt. 236 | * @param $password 237 | * @return bool 238 | */ 239 | function checkPassword($password) 240 | { 241 | if ($this->getMasterPassword() == null) 242 | return false; 243 | return $password == $this->getMasterPassword(); 244 | } 245 | 246 | /** 247 | * Time the session will take to expire in seconds. 248 | * 0 = until the session cookie expires. 249 | * @param $seconds 250 | */ 251 | function setSessionExpirationTime($seconds) 252 | { 253 | $_SESSION["session_expiration"] = $seconds; 254 | } 255 | 256 | function getSessionExpirationTime() 257 | { 258 | if ($this->isAuthenticated()) 259 | return $_SESSION["session_expiration"]; 260 | return null; 261 | } 262 | 263 | function getUserID() 264 | { 265 | if ($this->isAuthenticated()) 266 | return $_SESSION["userId"]; 267 | return null; 268 | } 269 | 270 | function getLastActivity() 271 | { 272 | if ($this->isAuthenticated()) 273 | return $_SESSION["last_activity"]; 274 | return null; 275 | } 276 | 277 | function getMasterPassword() 278 | { 279 | if ($this->isAuthenticated()) 280 | return $_SESSION["master_password"]; 281 | return null; 282 | } 283 | 284 | function isAuthenticated() 285 | { 286 | if (session_status() == PHP_SESSION_NONE) 287 | return false; 288 | if (!isset($_SESSION["username"]) || !isset($_SESSION["master_password"]) || !isset($_SESSION["userId"]) || !isset($_SESSION["ip"]) || !isset($_SESSION["last_activity"]) || !isset($_SESSION["session_expiration"])) 289 | return false; 290 | if ($_SESSION["ip"] != $_SERVER["REMOTE_ADDR"]) 291 | return false; 292 | return true; 293 | } 294 | 295 | } -------------------------------------------------------------------------------- /src/PASSY/Util.php: -------------------------------------------------------------------------------- 1 | 8 | * @package PASSY 9 | */ 10 | class Util 11 | { 12 | static function startsWith($haystack, $needle) 13 | { 14 | $length = strlen($needle); 15 | return $length == 0 ? true : (substr($haystack, 0, $length) === $needle); 16 | } 17 | 18 | static function endsWith($haystack, $needle) 19 | { 20 | $length = strlen($needle); 21 | 22 | return $length == 0 ? true : (substr($haystack, -$length) === $needle); 23 | } 24 | 25 | static function handleException($exception) 26 | { 27 | error_log($exception); 28 | $response = new Response(false, "server_error"); 29 | die($response->getJSONResponse()); 30 | } 31 | 32 | /** 33 | * Replaces special characters with HTML codes 34 | * @param $string string input string 35 | * @return string output string 36 | */ 37 | static function filterStrings($string) 38 | { 39 | return htmlspecialchars($string); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/PASSY/Validate.php: -------------------------------------------------------------------------------- 1 | = 3; 17 | } 18 | 19 | static function validateLoginPassword($password) 20 | { 21 | return strlen($password) >= 8; 22 | } 23 | } -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | (https://scrumplex.net) 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | function passy_autoload($class) 22 | { 23 | $path = realpath(__DIR__) . '/' . str_replace('\\', '/', $class) . '.php'; 24 | if (is_readable($path)) 25 | require_once $path; 26 | } 27 | 28 | spl_autoload_register('passy_autoload'); 29 | --------------------------------------------------------------------------------