├── .gitignore ├── Action.php ├── GoogleAuthenticator.php ├── Plugin.php ├── README.md ├── jquery.qrcode.min.js └── verification.php /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Action.php: -------------------------------------------------------------------------------- 1 | request->get('otp'))>0){ 22 | //获取到CODE 23 | if (isset($_SESSION['GAuthenticator'])&&$_SESSION['GAuthenticator']) return;//如果SESSION匹配则直接返回 24 | $config = Helper::options()->plugin('GAuthenticator'); 25 | require_once 'GoogleAuthenticator.php'; 26 | $referer = $this->request->getReferer(); 27 | $Authenticator = new PHPGangsta_GoogleAuthenticator();//初始化生成类 28 | $oneCode = intval($this->request->get('otp'));//手机端生成的一次性代码 29 | if($Authenticator->verifyCode($config->SecretKey, $oneCode, $config->SecretTime)){//验证一次性代码 30 | $expire = 1 == $this->request->get('remember') ? Helper::options()->time + Helper::options()->timezone + 30*24*3600 : 0; 31 | $_SESSION['GAuthenticator'] = true;//session保存 32 | Typecho_Cookie::set('__typecho_GAuthenticator',md5($config->SecretKey.Typecho_Cookie::getPrefix().Typecho_Widget::widget('Widget_User')->uid),$expire);//cookie保存 33 | }else{ 34 | Typecho_Widget::widget('Widget_Notice')->set(_t('两步验证失败'), 'error'); 35 | } 36 | $response = Typecho_Response::getInstance(); 37 | $this->response->redirect($referer); 38 | } 39 | } 40 | 41 | public function action(){ 42 | $this->widget('Widget_User')->pass('administrator'); 43 | $this->on($this->request->is('otp'))->auth(); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /GoogleAuthenticator.php: -------------------------------------------------------------------------------- 1 | _getBase32LookupTable(); 26 | unset($validChars[32]); 27 | 28 | $secret = ''; 29 | for ($i = 0; $i < $secretLength; $i++) { 30 | $secret .= $validChars[array_rand($validChars)]; 31 | } 32 | return $secret; 33 | } 34 | 35 | /** 36 | * Calculate the code, with given secret and point in time 37 | * 38 | * @param string $secret 39 | * @param int|null $timeSlice 40 | * @return string 41 | */ 42 | public function getCode($secret, $timeSlice = null) 43 | { 44 | if ($timeSlice === null) { 45 | $timeSlice = floor(time() / 30); 46 | } 47 | 48 | $secretkey = $this->_base32Decode($secret); 49 | 50 | // Pack time into binary string 51 | $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); 52 | // Hash it with users secret key 53 | $hm = hash_hmac('SHA1', $time, $secretkey, true); 54 | // Use last nipple of result as index/offset 55 | $offset = ord(substr($hm, -1)) & 0x0F; 56 | // grab 4 bytes of the result 57 | $hashpart = substr($hm, $offset, 4); 58 | 59 | // Unpak binary value 60 | $value = unpack('N', $hashpart); 61 | $value = $value[1]; 62 | // Only 32 bits 63 | $value = $value & 0x7FFFFFFF; 64 | 65 | $modulo = pow(10, $this->_codeLength); 66 | return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT); 67 | } 68 | 69 | /** 70 | * Get QR-Code URL for image, from google charts 71 | * 72 | * @param string $name 73 | * @param string $secret 74 | * @param string $title 75 | * @return string 76 | */ 77 | public function getQRCodeGoogleUrl($name, $secret, $title = null) { 78 | $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); 79 | if(isset($title)) { 80 | $urlencoded .= urlencode('&issuer='.urlencode($title)); 81 | } 82 | return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl='.$urlencoded.''; 83 | } 84 | 85 | /** 86 | * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now 87 | * 88 | * @param string $secret 89 | * @param string $code 90 | * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) 91 | * @param int|null $currentTimeSlice time slice if we want use other that time() 92 | * @return bool 93 | */ 94 | public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) 95 | { 96 | if ($currentTimeSlice === null) { 97 | $currentTimeSlice = floor(time() / 30); 98 | } 99 | 100 | for ($i = -$discrepancy; $i <= $discrepancy; $i++) { 101 | $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); 102 | if ($calculatedCode == $code ) { 103 | return true; 104 | } 105 | } 106 | 107 | return false; 108 | } 109 | 110 | /** 111 | * Set the code length, should be >=6 112 | * 113 | * @param int $length 114 | * @return PHPGangsta_GoogleAuthenticator 115 | */ 116 | public function setCodeLength($length) 117 | { 118 | $this->_codeLength = $length; 119 | return $this; 120 | } 121 | 122 | /** 123 | * Helper class to decode base32 124 | * 125 | * @param $secret 126 | * @return bool|string 127 | */ 128 | protected function _base32Decode($secret) 129 | { 130 | if (empty($secret)) return ''; 131 | 132 | $base32chars = $this->_getBase32LookupTable(); 133 | $base32charsFlipped = array_flip($base32chars); 134 | 135 | $paddingCharCount = substr_count($secret, $base32chars[32]); 136 | $allowedValues = array(6, 4, 3, 1, 0); 137 | if (!in_array($paddingCharCount, $allowedValues)) return false; 138 | for ($i = 0; $i < 4; $i++){ 139 | if ($paddingCharCount == $allowedValues[$i] && 140 | substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) return false; 141 | } 142 | $secret = str_replace('=','', $secret); 143 | $secret = str_split($secret); 144 | $binaryString = ""; 145 | for ($i = 0; $i < count($secret); $i = $i+8) { 146 | $x = ""; 147 | if (!in_array($secret[$i], $base32chars)) return false; 148 | for ($j = 0; $j < 8; $j++) { 149 | $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); 150 | } 151 | $eightBits = str_split($x, 8); 152 | for ($z = 0; $z < count($eightBits); $z++) { 153 | $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:""; 154 | } 155 | } 156 | return $binaryString; 157 | } 158 | 159 | /** 160 | * Helper class to encode base32 161 | * 162 | * @param string $secret 163 | * @param bool $padding 164 | * @return string 165 | */ 166 | protected function _base32Encode($secret, $padding = true) 167 | { 168 | if (empty($secret)) return ''; 169 | 170 | $base32chars = $this->_getBase32LookupTable(); 171 | 172 | $secret = str_split($secret); 173 | $binaryString = ""; 174 | for ($i = 0; $i < count($secret); $i++) { 175 | $binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT); 176 | } 177 | $fiveBitBinaryArray = str_split($binaryString, 5); 178 | $base32 = ""; 179 | $i = 0; 180 | while ($i < count($fiveBitBinaryArray)) { 181 | $base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)]; 182 | $i++; 183 | } 184 | if ($padding && ($x = strlen($binaryString) % 40) != 0) { 185 | if ($x == 8) $base32 .= str_repeat($base32chars[32], 6); 186 | elseif ($x == 16) $base32 .= str_repeat($base32chars[32], 4); 187 | elseif ($x == 24) $base32 .= str_repeat($base32chars[32], 3); 188 | elseif ($x == 32) $base32 .= $base32chars[32]; 189 | } 190 | return $base32; 191 | } 192 | 193 | /** 194 | * Get array with all 32 characters for decoding from/encoding to base32 195 | * 196 | * @return array 197 | */ 198 | protected function _getBase32LookupTable() 199 | { 200 | return array( 201 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 202 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 203 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 204 | 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 205 | '=' // padding char 206 | ); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /Plugin.php: -------------------------------------------------------------------------------- 1 | navBar = array(__CLASS__, 'Authenticator_safe'); 25 | Typecho_Plugin::factory('admin/common.php')->begin = array(__CLASS__, 'Authenticator_verification'); 26 | return _t('当前两步验证还未启用,请进行初始化设置'); 27 | } 28 | 29 | /** 30 | * 禁用插件方法,如果禁用失败,直接抛出异常 31 | * 32 | * @static 33 | * @access public 34 | * @return void 35 | * @throws Typecho_Plugin_Exception 36 | */ 37 | public static function deactivate(){ 38 | Helper::removeRoute('GAuthenticator'); 39 | } 40 | 41 | /** 42 | * 获取插件配置面板 43 | * 44 | * @access public 45 | * @param Typecho_Widget_Helper_Form $form 配置面板 46 | * @return void' 47 | */ 48 | public static function config(Typecho_Widget_Helper_Form $form) 49 | { 50 | $qrurl = 'otpauth://totp/'.urlencode(Helper::options()->title.':'.Typecho_Widget::widget('Widget_User')->mail).'?secret='; 51 | $element = new Typecho_Widget_Helper_Form_Element_Text('SecretKey', NULL, '', _t('SecretKey'), ' 52 | 安装的时候自动计算密钥,手动修改无效,如需要修改请卸载重新安装或者手动修改数据库
53 |
54 | 请扫描下面的二维码绑定
55 |
56 |
57 | '); 65 | $form->addInput($element); 66 | $element = new Typecho_Widget_Helper_Form_Element_Text('SecretQRurl', NULL, '', _t('二维码的网址'), '本选项已过时,保留只是为了向下兼容。和上面图片的地址是相同的'); 67 | $form->addInput($element); 68 | $element = new Typecho_Widget_Helper_Form_Element_Text('SecretTime', NULL, 2, _t('容差时间'), '允许的容差时间,单位为30秒的倍数,如果这里是2 那么就是 2* 30 sec 一分钟.'); 69 | $form->addInput($element); 70 | $element = new Typecho_Widget_Helper_Form_Element_Text('SecretCode', NULL, '', _t('客户端代码'), '输入你APP或者其他什么鬼上面显示的六位数字。
用兼容的APP上面的扫描二维码或者手动输入第一行的SecretKey即可生成'); 71 | $form->addInput($element); 72 | $element = new Typecho_Widget_Helper_Form_Element_Radio('SecretOn', array('1' => '开启','0' => '关闭'), 0, _t('插件开关'), '这里关掉了,就不需要验证即可登录。'); 73 | $form->addInput($element); 74 | } 75 | /** 76 | * 手动保存配置面板 77 | * @param $config array 插件配置 78 | * @param $is_init bool 是否初始化 79 | */ 80 | public static function configHandle($config, $is_init) 81 | { 82 | if ($is_init) {//如果是第一次初始化插件 83 | require_once 'GoogleAuthenticator.php'; 84 | $Authenticator = new PHPGangsta_GoogleAuthenticator();//初始化生成类 85 | $config['SecretKey'] = $Authenticator->createSecret();//生成一个随机安全密钥 86 | $config['SecretQRurl'] = 'http://qr.liantu.com/api.php?text='.urlencode('otpauth://totp/'.urlencode(Helper::options()->title.':'.Typecho_Widget::widget('Widget_User')->mail).'?secret='.$config['SecretKey']);//生成安全密钥的二维码网址 87 | }else{ 88 | $config_old = Helper::options()->plugin(self::$pluginName); 89 | if(($config['SecretCode']!='' && $config['SecretOn']==1) || $config['SecretOn']==1){//如果启用,并且验证码不为空 90 | require_once 'GoogleAuthenticator.php'; 91 | $Authenticator = new PHPGangsta_GoogleAuthenticator(); 92 | if($Authenticator->verifyCode($config['SecretKey'], $config['SecretCode'], $config['SecretTime'])){ 93 | $config['SecretOn'] = 1;//如果匹配,则启用 94 | }else{ 95 | throw new Typecho_Plugin_Exception('两步验证代码校验失败,请重试或选择关闭'); 96 | } 97 | } 98 | $config['SecretKey'] = $config_old->SecretKey;//保持初始化SecretKey不被修改 99 | $config['SecretQRurl'] = $config_old->SecretQRurl;//保持初始化SecretQRurl不被修改 过时选项 兼容保留 100 | } 101 | $config['SecretCode'] = '';//每次保存不保存验证码 102 | Helper::configPlugin(self::$pluginName, $config);//保存插件配置 103 | } 104 | /** 105 | * 个人用户的配置面板 106 | * 107 | * @access public 108 | * @param Typecho_Widget_Helper_Form $form 109 | * @return void 110 | */ 111 | public static function personalConfig(Typecho_Widget_Helper_Form $form){} 112 | 113 | /** 114 | * 插件实现方法 115 | * 116 | * @access public 117 | * @return void 118 | */ 119 | public static function Authenticator_safe() 120 | { 121 | $config = Helper::options()->plugin(self::$pluginName); 122 | if($config->SecretOn==1){ 123 | echo ''.htmlspecialchars('已启用 Authenticator 验证').''; 124 | }else{ 125 | echo ''.htmlspecialchars('未启用 Authenticator 验证').''; 126 | } 127 | } 128 | 129 | public static function Authenticator_verification() 130 | { 131 | if(isset($Authenticator_init))return; 132 | $Authenticator_init = true; 133 | if (!Typecho_Widget::widget('Widget_User')->hasLogin()){ 134 | return;//如果没登录则直接返回 135 | }else{ 136 | //已经登录就验证 137 | $config = Helper::options()->plugin(self::$pluginName); 138 | if (isset($_SESSION['GAuthenticator'])&&$_SESSION['GAuthenticator']) return;//如果SESSION匹配则直接返回 139 | if (Typecho_Cookie::get('__typecho_GAuthenticator') == md5($config->SecretKey.Typecho_Cookie::getPrefix().Typecho_Widget::widget('Widget_User')->uid)) return;//如果COOKIE匹配则直接返回 140 | if($config->SecretOn==1){ 141 | $options = Helper::options(); 142 | $request = new Typecho_Request(); 143 | require_once 'verification.php'; 144 | }else{ 145 | return;//如果未开启插件则直接返回 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GAuthenticator 2 | 3 | 一个 Typecho 二次认证插件 4 | 5 | ## 本版特点 6 | 7 | 感谢原作者 [@WeiCN](https://github.com/naicfeng) ,修复了 Typecho 1.2 的兼容性问题。 8 | 9 | 相对于旧版,新版的验证逻辑**全部更新**,推荐升级! 10 | 11 | - 支持验证态保持,成功登录后,在 session 或 cookie 有效期内无需再次验证; 12 | - 采用插件内注册的 Route 来处理 OTP,无需等待 TP 返回的 2s 后验证; 13 | 14 | 请注意:从 0.0.1 升级到 0.0.2+ 版本需要**卸载重新安装**! 15 | 16 | 兼容所有符合 [RFC6238](https://tools.ietf.org/html/rfc6238 "rfc6238") 规范的 AuthOTP 软件。 17 | 18 | - Microsoft Authenticator 19 | - Google Authenticator 20 | - 1Password 21 | - Authy 22 | - KeePass 23 | - LastPass 24 | - ... 25 | 26 | ## 更新说明 27 | 28 | ### 1.0.1 29 | - [fix] 修复 1.2 版本报错问题 30 | 31 | ### 0.0.6 32 | - [change] 使用 `jquery-qrcode` 插件在浏览器端生成二维码(不再使用外站的API来生成二维码,保证Key的安全性). 33 | 34 | ### 0.0.5 35 | - [fix] 修复启用插件500错误,改为使用jQuery获取SecretKey显示二维码 36 | 37 | ### 0.0.4 38 | - [add] 支持后台直接显示二维码 39 | - [fix] 修改为使用联图API显示二维码 40 | - [fix] 修复博客名称为中文时扫描二维码提示错误 41 | - [fix] 修复卸载的时候没有删除路由 42 | - [fix] 登录成功后主动访问路由地址会显示一条msg 验证失败 43 | 44 | ### 0.0.3 45 | - [add] 更新支持记住本机 46 | 47 | ### 0.0.2 48 | - [add] 支持typecho最新版 49 | - [feature] 流程优化,符合大多数网站逻辑 50 | -------------------------------------------------------------------------------- /jquery.qrcode.min.js: -------------------------------------------------------------------------------- 1 | (function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){this.typeNumber=a;this.errorCorrectLevel=c;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[]}function q(a,c){if(void 0==a.length)throw Error(a.length+"/"+c);for(var d=0;da||this.moduleCount<=a||0>c||this.moduleCount<=c)throw Error(a+","+c);return this.modules[a][c]},getModuleCount:function(){return this.moduleCount},make:function(){if(1>this.typeNumber){for(var a=1,a=1;40>a;a++){for(var c=p.getRSBlocks(a,this.errorCorrectLevel),d=new t,b=0,e=0;e=d;d++)if(!(-1>=a+d||this.moduleCount<=a+d))for(var b=-1;7>=b;b++)-1>=c+b||this.moduleCount<=c+b||(this.modules[a+d][c+b]= 5 | 0<=d&&6>=d&&(0==b||6==b)||0<=b&&6>=b&&(0==d||6==d)||2<=d&&4>=d&&2<=b&&4>=b?!0:!1)},getBestMaskPattern:function(){for(var a=0,c=0,d=0;8>d;d++){this.makeImpl(!0,d);var b=j.getLostPoint(this);if(0==d||a>b)a=b,c=d}return c},createMovieClip:function(a,c,d){a=a.createEmptyMovieClip(c,d);this.make();for(c=0;c=f;f++)for(var i=-2;2>=i;i++)this.modules[b+f][e+i]=-2==f||2==f||-2==i||2==i||0==f&&0==i?!0:!1}},setupTypeNumber:function(a){for(var c= 7 | j.getBCHTypeNumber(this.typeNumber),d=0;18>d;d++){var b=!a&&1==(c>>d&1);this.modules[Math.floor(d/3)][d%3+this.moduleCount-8-3]=b}for(d=0;18>d;d++)b=!a&&1==(c>>d&1),this.modules[d%3+this.moduleCount-8-3][Math.floor(d/3)]=b},setupTypeInfo:function(a,c){for(var d=j.getBCHTypeInfo(this.errorCorrectLevel<<3|c),b=0;15>b;b++){var e=!a&&1==(d>>b&1);6>b?this.modules[b][8]=e:8>b?this.modules[b+1][8]=e:this.modules[this.moduleCount-15+b][8]=e}for(b=0;15>b;b++)e=!a&&1==(d>>b&1),8>b?this.modules[8][this.moduleCount- 8 | b-1]=e:9>b?this.modules[8][15-b-1+1]=e:this.modules[8][15-b-1]=e;this.modules[this.moduleCount-8][8]=!a},mapData:function(a,c){for(var d=-1,b=this.moduleCount-1,e=7,f=0,i=this.moduleCount-1;0g;g++)if(null==this.modules[b][i-g]){var n=!1;f>>e&1));j.getMask(c,b,i-g)&&(n=!n);this.modules[b][i-g]=n;e--; -1==e&&(f++,e=7)}b+=d;if(0>b||this.moduleCount<=b){b-=d;d=-d;break}}}};o.PAD0=236;o.PAD1=17;o.createData=function(a,c,d){for(var c=p.getRSBlocks(a, 9 | c),b=new t,e=0;e8*a)throw Error("code length overflow. ("+b.getLengthInBits()+">"+8*a+")");for(b.getLengthInBits()+4<=8*a&&b.put(0,4);0!=b.getLengthInBits()%8;)b.putBit(!1);for(;!(b.getLengthInBits()>=8*a);){b.put(o.PAD0,8);if(b.getLengthInBits()>=8*a)break;b.put(o.PAD1,8)}return o.createBytes(b,c)};o.createBytes=function(a,c){for(var d= 10 | 0,b=0,e=0,f=Array(c.length),i=Array(c.length),g=0;g>>=1;return c},getPatternPosition:function(a){return j.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,c,d){switch(a){case 0:return 0==(c+d)%2;case 1:return 0==c%2;case 2:return 0==d%3;case 3:return 0==(c+d)%3;case 4:return 0==(Math.floor(c/2)+Math.floor(d/3))%2;case 5:return 0==c*d%2+c*d%3;case 6:return 0==(c*d%2+c*d%3)%2;case 7:return 0==(c*d%3+(c+d)%2)%2;default:throw Error("bad maskPattern:"+ 14 | a);}},getErrorCorrectPolynomial:function(a){for(var c=new q([1],0),d=0;dc)switch(a){case 1:return 10;case 2:return 9;case s:return 8;case 8:return 8;default:throw Error("mode:"+a);}else if(27>c)switch(a){case 1:return 12;case 2:return 11;case s:return 16;case 8:return 10;default:throw Error("mode:"+a);}else if(41>c)switch(a){case 1:return 14;case 2:return 13;case s:return 16;case 8:return 12;default:throw Error("mode:"+ 15 | a);}else throw Error("type:"+c);},getLostPoint:function(a){for(var c=a.getModuleCount(),d=0,b=0;b=g;g++)if(!(0>b+g||c<=b+g))for(var h=-1;1>=h;h++)0>e+h||c<=e+h||0==g&&0==h||i==a.isDark(b+g,e+h)&&f++;5a)throw Error("glog("+a+")");return l.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;256<=a;)a-=255;return l.EXP_TABLE[a]},EXP_TABLE:Array(256), 17 | LOG_TABLE:Array(256)},m=0;8>m;m++)l.EXP_TABLE[m]=1<m;m++)l.EXP_TABLE[m]=l.EXP_TABLE[m-4]^l.EXP_TABLE[m-5]^l.EXP_TABLE[m-6]^l.EXP_TABLE[m-8];for(m=0;255>m;m++)l.LOG_TABLE[l.EXP_TABLE[m]]=m;q.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var c=Array(this.getLength()+a.getLength()-1),d=0;d 18 | this.getLength()-a.getLength())return this;for(var c=l.glog(this.get(0))-l.glog(a.get(0)),d=Array(this.getLength()),b=0;b>>7-a%8&1)},put:function(a,c){for(var d=0;d>>c-d-1&1))},getLengthInBits:function(){return this.length},putBit:function(a){var c=Math.floor(this.length/8);this.buffer.length<=c&&this.buffer.push(0);a&&(this.buffer[c]|=128>>>this.length%8);this.length++}};"string"===typeof h&&(h={text:h});h=r.extend({},{render:"canvas",width:256,height:256,typeNumber:-1, 26 | correctLevel:2,background:"#ffffff",foreground:"#000000"},h);return this.each(function(){var a;if("canvas"==h.render){a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();var c=document.createElement("canvas");c.width=h.width;c.height=h.height;for(var d=c.getContext("2d"),b=h.width/a.getModuleCount(),e=h.height/a.getModuleCount(),f=0;f").css("width",h.width+"px").css("height",h.height+"px").css("border","0px").css("border-collapse","collapse").css("background-color",h.background);d=h.width/a.getModuleCount();b=h.height/a.getModuleCount();for(e=0;e").css("height",b+"px").appendTo(c);for(i=0;i").css("width", 28 | d+"px").css("background-color",a.isDark(e,i)?h.foreground:h.background).appendTo(f)}}a=c;jQuery(a).appendTo(this)})}})(jQuery); 29 | -------------------------------------------------------------------------------- /verification.php: -------------------------------------------------------------------------------- 1 | rewrite) ? $options->siteUrl : $options->siteUrl . 'index.php'; 10 | $url = rtrim($url, '/') . '/GAuthenticator'; 11 | 12 | include 'header.php'; 13 | ?> 14 | 42 | 47 | --------------------------------------------------------------------------------