|
@@ -42,37 +42,236 @@ class Index extends Frontend
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 当前手机端登录用户;未登录返回 null
|
|
|
|
|
|
|
+ * 当前手机端登录用户;未登录返回 null(支持 Cookie 令牌 + 7 天记住登录)
|
|
|
*/
|
|
*/
|
|
|
protected function mprocGetUser()
|
|
protected function mprocGetUser()
|
|
|
|
|
+ {
|
|
|
|
|
+ $token = $this->mprocReadTokenFromRequest();
|
|
|
|
|
+ if ($token !== '') {
|
|
|
|
|
+ $user = $this->mprocLoadUserByToken($token);
|
|
|
|
|
+ if ($user) {
|
|
|
|
|
+ $this->mprocTouchLoginState($user, $token);
|
|
|
|
|
+
|
|
|
|
|
+ return $user;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $user = $this->mprocUserFromRememberCookie();
|
|
|
|
|
+ if (!$user) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ $token = bin2hex(random_bytes(16));
|
|
|
|
|
+ $this->mprocTouchLoginState($user, $token);
|
|
|
|
|
+ $user['token'] = $token;
|
|
|
|
|
+
|
|
|
|
|
+ return $user;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected function mprocReadTokenFromRequest(): string
|
|
|
{
|
|
{
|
|
|
$token = Session::get('mproc_token');
|
|
$token = Session::get('mproc_token');
|
|
|
if ($token === null || $token === '') {
|
|
if ($token === null || $token === '') {
|
|
|
$token = Cookie::get('mproc_token');
|
|
$token = Cookie::get('mproc_token');
|
|
|
}
|
|
}
|
|
|
if ($token === null || $token === '') {
|
|
if ($token === null || $token === '') {
|
|
|
- return null;
|
|
|
|
|
|
|
+ $token = $this->request->header('X-Mproc-Token');
|
|
|
}
|
|
}
|
|
|
- $token = preg_replace('/[^a-f0-9]/i', '', (string)$token);
|
|
|
|
|
- if (strlen($token) < 16) {
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ if ($token === null || $token === '') {
|
|
|
|
|
+ $token = $this->request->request('mproc_token', '');
|
|
|
}
|
|
}
|
|
|
|
|
+ $token = preg_replace('/[^a-f0-9]/i', '', (string)$token);
|
|
|
|
|
+
|
|
|
|
|
+ return strlen($token) >= 16 ? $token : '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return array<string, mixed>|null
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function mprocLoadUserByToken(string $token): ?array
|
|
|
|
|
+ {
|
|
|
$user = Cache::get('mproc_u_' . $token);
|
|
$user = Cache::get('mproc_u_' . $token);
|
|
|
if (!is_array($user)) {
|
|
if (!is_array($user)) {
|
|
|
- return null;
|
|
|
|
|
|
|
+ $user = $this->mprocUserFromRememberCookie();
|
|
|
|
|
+ if (!$user) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- // 手机登录必有 phone;账号密码登录必有 username
|
|
|
|
|
if (empty($user['phone']) && empty($user['account']) && empty($user['username'])) {
|
|
if (empty($user['phone']) && empty($user['account']) && empty($user['username'])) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
if (time() - (int)($user['login_time'] ?? 0) > $this->mprocTtlSeconds) {
|
|
if (time() - (int)($user['login_time'] ?? 0) > $this->mprocTtlSeconds) {
|
|
|
$this->mprocClearLogin($token);
|
|
$this->mprocClearLogin($token);
|
|
|
|
|
+
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
$user['token'] = $token;
|
|
$user['token'] = $token;
|
|
|
|
|
+
|
|
|
return $user;
|
|
return $user;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 滑动续期:刷新 Cache / Session / Cookie(保留 7 天)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<string, mixed> $user
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function mprocTouchLoginState(array $user, string $token): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $user['login_time'] = time();
|
|
|
|
|
+ Cache::set('mproc_u_' . $token, $user, $this->mprocTtlSeconds + 86400);
|
|
|
|
|
+ Session::set('mproc_token', $token);
|
|
|
|
|
+ $this->mprocSetTokenCookie($token);
|
|
|
|
|
+ $this->mprocSetRememberCookie($user);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected function mprocSetTokenCookie(string $token): void
|
|
|
|
|
+ {
|
|
|
|
|
+ Cookie::set('mproc_token', $token, [
|
|
|
|
|
+ 'expire' => $this->mprocTtlSeconds,
|
|
|
|
|
+ 'path' => '/',
|
|
|
|
|
+ 'httponly' => true,
|
|
|
|
|
+ 'samesite' => 'Lax',
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param array<string, mixed> $user
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function mprocSetRememberCookie(array $user): void
|
|
|
|
|
+ {
|
|
|
|
|
+ Cookie::set('mproc_remember', $this->mprocPackRememberCookie($user), [
|
|
|
|
|
+ 'expire' => $this->mprocTtlSeconds,
|
|
|
|
|
+ 'path' => '/',
|
|
|
|
|
+ 'httponly' => true,
|
|
|
|
|
+ 'samesite' => 'Lax',
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected function mprocAuthSignSecret(): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $key = trim((string)Config::get('mproc.auth_sign_key'));
|
|
|
|
|
+ if ($key === '') {
|
|
|
|
|
+ $key = (string)Config::get('database.database') . '|' . (string)Config::get('database.hostname') . '|mproc';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return hash('sha256', $key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param array<string, mixed> $user
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function mprocPackRememberCookie(array $user): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $payload = [
|
|
|
|
|
+ 'uid' => (int)($user['customer_user_id'] ?? $user['customer_id'] ?? 0),
|
|
|
|
|
+ 'phone' => trim((string)($user['phone'] ?? '')),
|
|
|
|
|
+ 'uname' => trim((string)($user['username'] ?? $user['account'] ?? '')),
|
|
|
|
|
+ 'admin' => !empty($user['is_admin']) ? 1 : 0,
|
|
|
|
|
+ 'lt' => time(),
|
|
|
|
|
+ ];
|
|
|
|
|
+ $b64 = rtrim(strtr(base64_encode(json_encode($payload, JSON_UNESCAPED_UNICODE)), '+/', '-_'), '=');
|
|
|
|
|
+ $sig = hash_hmac('sha256', $b64, $this->mprocAuthSignSecret());
|
|
|
|
|
+
|
|
|
|
|
+ return $b64 . '.' . $sig;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return array<string, mixed>|null
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function mprocUserFromRememberCookie(): ?array
|
|
|
|
|
+ {
|
|
|
|
|
+ $raw = Cookie::get('mproc_remember');
|
|
|
|
|
+ if ($raw === null || $raw === '') {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ $parts = explode('.', (string)$raw, 2);
|
|
|
|
|
+ if (count($parts) !== 2) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ $b64 = $parts[0];
|
|
|
|
|
+ $sig = $parts[1];
|
|
|
|
|
+ if (!hash_equals(hash_hmac('sha256', $b64, $this->mprocAuthSignSecret()), $sig)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ $pad = strlen($b64) % 4;
|
|
|
|
|
+ if ($pad > 0) {
|
|
|
|
|
+ $b64 .= str_repeat('=', 4 - $pad);
|
|
|
|
|
+ }
|
|
|
|
|
+ $json = base64_decode(strtr($b64, '-_', '+/'), true);
|
|
|
|
|
+ if ($json === false || $json === '') {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ $payload = json_decode($json, true);
|
|
|
|
|
+ if (!is_array($payload)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ $lt = (int)($payload['lt'] ?? 0);
|
|
|
|
|
+ if ($lt <= 0 || time() - $lt > $this->mprocTtlSeconds) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $this->mprocRebuildUserFromRememberPayload($payload, $lt);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param array<string, mixed> $payload
|
|
|
|
|
+ * @return array<string, mixed>|null
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function mprocRebuildUserFromRememberPayload(array $payload, int $loginTime): ?array
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!empty($payload['admin'])) {
|
|
|
|
|
+ $uname = trim((string)($payload['uname'] ?? ''));
|
|
|
|
|
+ if ($uname === '') {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ $row = Db::name('admin')->where('username', $uname)->find();
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ $row = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!is_array($row) || ($row['status'] ?? '') === 'hidden') {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'phone' => trim((string)($row['mobile'] ?? '')),
|
|
|
|
|
+ 'company_name' => '',
|
|
|
|
|
+ 'username' => $uname,
|
|
|
|
|
+ 'customer_user_id' => 0,
|
|
|
|
|
+ 'login_type' => 'remember',
|
|
|
|
|
+ 'is_admin' => 1,
|
|
|
|
|
+ 'login_time' => $loginTime,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $cid = (int)($payload['uid'] ?? 0);
|
|
|
|
|
+ if ($cid > 0) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ $cu = Db::table('customer')->where('id', $cid)->find();
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ $cu = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (is_array($cu) && $cu !== [] && $this->mprocCustomerUserActive($cu)) {
|
|
|
|
|
+ $user = $this->mprocLoginPayloadFromCustomer($cu, 'remember');
|
|
|
|
|
+ $user['login_time'] = $loginTime;
|
|
|
|
|
+
|
|
|
|
|
+ return $user;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $phone = trim((string)($payload['phone'] ?? ''));
|
|
|
|
|
+ if ($phone !== '' && preg_match('/^1\d{10}$/', $phone)) {
|
|
|
|
|
+ $cu = $this->mprocFindCustomerUserByMobile($phone);
|
|
|
|
|
+ if ($cu) {
|
|
|
|
|
+ $user = $this->mprocLoginPayloadFromCustomer($cu, 'remember');
|
|
|
|
|
+ $user['login_time'] = $loginTime;
|
|
|
|
|
+
|
|
|
|
|
+ return $user;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
protected function mprocClearLogin($token)
|
|
protected function mprocClearLogin($token)
|
|
|
{
|
|
{
|
|
|
if ($token !== null && $token !== '') {
|
|
if ($token !== null && $token !== '') {
|
|
@@ -80,6 +279,7 @@ class Index extends Frontend
|
|
|
}
|
|
}
|
|
|
Session::delete('mproc_token');
|
|
Session::delete('mproc_token');
|
|
|
Cookie::delete('mproc_token');
|
|
Cookie::delete('mproc_token');
|
|
|
|
|
+ Cookie::delete('mproc_remember');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -379,16 +579,17 @@ class Index extends Frontend
|
|
|
}
|
|
}
|
|
|
$token = bin2hex(random_bytes(16));
|
|
$token = bin2hex(random_bytes(16));
|
|
|
$userData['login_time'] = time();
|
|
$userData['login_time'] = time();
|
|
|
- Cache::set('mproc_u_' . $token, $userData, $this->mprocTtlSeconds + 86400);
|
|
|
|
|
- Session::set('mproc_token', $token);
|
|
|
|
|
- Cookie::set('mproc_token', $token, $this->mprocTtlSeconds);
|
|
|
|
|
|
|
+ $this->mprocTouchLoginState($userData, $token);
|
|
|
|
|
|
|
|
$postR = $this->mprocSanitizeRedirectUrl($this->request->post('redirect', ''));
|
|
$postR = $this->mprocSanitizeRedirectUrl($this->request->post('redirect', ''));
|
|
|
$sessR = $this->mprocSanitizeRedirectUrl((string)Session::get('mproc_intended_url', ''));
|
|
$sessR = $this->mprocSanitizeRedirectUrl((string)Session::get('mproc_intended_url', ''));
|
|
|
Session::delete('mproc_intended_url');
|
|
Session::delete('mproc_intended_url');
|
|
|
$raw = $postR !== '' ? $postR : $sessR;
|
|
$raw = $postR !== '' ? $postR : $sessR;
|
|
|
$jump = $this->mprocBuildAfterLoginIndexUrl($raw);
|
|
$jump = $this->mprocBuildAfterLoginIndexUrl($raw);
|
|
|
- $this->success('登录成功', $jump);
|
|
|
|
|
|
|
+ $this->success('登录成功', $jump, [
|
|
|
|
|
+ 'mproc_token' => $token,
|
|
|
|
|
+ 'keep_days' => max(1, (int)round($this->mprocTtlSeconds / 86400)),
|
|
|
|
|
+ ]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1183,7 +1384,7 @@ class Index extends Frontend
|
|
|
if ($safe !== '') {
|
|
if ($safe !== '') {
|
|
|
Session::set('mproc_intended_url', $safe);
|
|
Session::set('mproc_intended_url', $safe);
|
|
|
}
|
|
}
|
|
|
- $this->redirect($this->mprocBuildLoginUrl(''));
|
|
|
|
|
|
|
+ $this->redirect($this->mprocBuildLoginUrl($safe));
|
|
|
|
|
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -1307,10 +1508,10 @@ class Index extends Frontend
|
|
|
*/
|
|
*/
|
|
|
public function login()
|
|
public function login()
|
|
|
{
|
|
{
|
|
|
|
|
+ $redirect = $this->mprocSanitizeRedirectUrl($this->request->get('redirect', ''));
|
|
|
if ($this->mprocGetUser()) {
|
|
if ($this->mprocGetUser()) {
|
|
|
- $this->redirect(url('index/index/index'));
|
|
|
|
|
|
|
+ $this->redirect($this->mprocBuildAfterLoginIndexUrl($redirect));
|
|
|
}
|
|
}
|
|
|
- $redirect = $this->mprocSanitizeRedirectUrl($this->request->get('redirect', ''));
|
|
|
|
|
if ($redirect !== '') {
|
|
if ($redirect !== '') {
|
|
|
Session::set('mproc_intended_url', $redirect);
|
|
Session::set('mproc_intended_url', $redirect);
|
|
|
}
|
|
}
|
|
@@ -1414,6 +1615,32 @@ class Index extends Frontend
|
|
|
$this->mprocFinishLogin($this->mprocLoginPayloadFromCustomer($cu, 'sms'));
|
|
$this->mprocFinishLogin($this->mprocLoginPayloadFromCustomer($cu, 'sms'));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 用本地保存的 token 恢复登录态(POST:mproc_token)
|
|
|
|
|
+ */
|
|
|
|
|
+ public function mprocRestore()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!$this->request->isPost()) {
|
|
|
|
|
+ $this->error('请使用 POST');
|
|
|
|
|
+ }
|
|
|
|
|
+ $token = $this->mprocReadTokenFromRequest();
|
|
|
|
|
+ if ($token === '') {
|
|
|
|
|
+ $this->error('登录已过期,请重新登录', url('index/index/login'));
|
|
|
|
|
+ }
|
|
|
|
|
+ $user = $this->mprocLoadUserByToken($token);
|
|
|
|
|
+ if (!$user) {
|
|
|
|
|
+ $user = $this->mprocUserFromRememberCookie();
|
|
|
|
|
+ if ($user) {
|
|
|
|
|
+ $token = bin2hex(random_bytes(16));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!$user) {
|
|
|
|
|
+ $this->error('登录已过期,请重新登录', url('index/index/login'));
|
|
|
|
|
+ }
|
|
|
|
|
+ $this->mprocTouchLoginState($user, $token);
|
|
|
|
|
+ $this->success('ok', '', ['mproc_token' => $token]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 账号密码登录(POST:username、password)
|
|
* 账号密码登录(POST:username、password)
|
|
|
* 先 customer(account),未命中再 admin;admin 密码规则同 FastAdmin Auth::login
|
|
* 先 customer(account),未命中再 admin;admin 密码规则同 FastAdmin Auth::login
|