浏览代码

提交maxkey自定义扫码和社交服务绑定过程

shibanglin 2 年之前
父节点
当前提交
fd26bb701b
共有 15 个文件被更改,包括 453 次插入14 次删除
  1. 2 2
      maxkey-web-frontend/maxkey-web-app/src/app/routes/config/socials-provider/socials-provider.component.ts
  2. 31 2
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/callback.component.ts
  3. 5 2
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.html
  4. 2 1
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.less
  5. 52 1
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.ts
  6. 2 2
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport.module.ts
  7. 30 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.html
  8. 0 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.less
  9. 42 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.spec.ts
  10. 124 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.ts
  11. 4 0
      maxkey-web-frontend/maxkey-web-app/src/app/service/authn.service.ts
  12. 8 0
      maxkey-web-frontend/maxkey-web-app/src/app/service/socials-provider.service.ts
  13. 146 0
      maxkey-web-frontend/maxkey-web-app/src/assets/qrcode/qrcode.min.js
  14. 1 1
      maxkey-web-frontend/maxkey-web-app/src/environments/environment.ts
  15. 4 3
      maxkey-web-frontend/maxkey-web-app/src/index.html

+ 2 - 2
maxkey-web-frontend/maxkey-web-app/src/app/routes/config/socials-provider/socials-provider.component.ts

@@ -123,7 +123,7 @@ export class SocialsProviderComponent implements OnInit {
   onAdd(e: MouseEvent): void {
     e.preventDefault();
     const modal = this.modalService.create({
-      //nzContent: SocialsProviderEditerComponent,
+      //nzContent: SocialsProviderBindUserComponent,
       nzViewContainerRef: this.viewContainerRef,
       nzComponentParams: {
         isEdit: false,
@@ -143,7 +143,7 @@ export class SocialsProviderComponent implements OnInit {
     e.preventDefault();
 
     const modal = this.modalService.create({
-      //nzContent: SocialsProviderEditerComponent,
+      //nzContent: SocialsProviderBindUserComponent,
       nzViewContainerRef: this.viewContainerRef,
       nzComponentParams: {
         isEdit: true,

+ 31 - 2
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/callback.component.ts

@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-import { Inject, Optional, Component, OnInit } from '@angular/core';
+import { Inject, Optional, Component, OnInit, ViewContainerRef } from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
 import { ReuseTabService } from '@delon/abc/reuse-tab';
 import { SettingsService } from '@delon/theme';
-
+import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal';
 import { AuthnService } from '../../service/authn.service';
 import { SocialsProviderService } from '../../service/socials-provider.service';
+import {SocialsProviderBindUserComponent} from "./socials-provider-bind-user/socials-provider-bind-user.component";
 
 @Component({
   selector: 'app-callback',
@@ -30,6 +31,8 @@ export class CallbackComponent implements OnInit {
   provider = '';
 
   constructor(
+    private viewContainerRef: ViewContainerRef,
+    private modalService: NzModalService,
     private router: Router,
     private socialsProviderService: SocialsProviderService,
     private settingsService: SettingsService,
@@ -50,6 +53,11 @@ export class CallbackComponent implements OnInit {
           // 设置用户Token信息
           this.authnService.auth(res.data);
         }
+        else if (res.code === 102) {
+          //绑定用户
+          this.openBindUser(res.message)
+          return;
+        }
         this.authnService.navigate({});
       });
     } else {
@@ -60,4 +68,25 @@ export class CallbackComponent implements OnInit {
       });
     }
   }
+
+  openBindUser(socialUserId: String) {
+    console.log("bind user : ",this.provider,socialUserId);
+    const modal = this.modalService.create({
+      nzContent: SocialsProviderBindUserComponent,
+      nzViewContainerRef: this.viewContainerRef,
+      nzComponentParams: {
+        socialUserId: socialUserId,
+        provider: this.provider
+      },
+      nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+    });
+    // Return a result when closed
+    modal.afterClose.subscribe(result => {
+      if (result.refresh) {
+
+      }
+    });
+
+
+  }
 }

+ 5 - 2
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.html

@@ -82,8 +82,11 @@
       </nz-form-control>
     </nz-form-item>
   </div>
-  <div nz-row style="{{ loginType == 'qrscan' ? '' : 'display: none;' }}">
-    <div class="qrcode" id="div_qrcodelogin"> </div>
+  <div nz-row *ngIf="loginType=='qrscan'">
+    <div class="qrcode" id="div_qrcodelogin" style="background: #fff;padding: 20px;"> </div>
+    <div id="qrexpire" *ngIf="qrexpire" style="width: 100%;text-align: center;background: #fff;padding-bottom: 20px;">
+      二维码过期 <a href="javascript:" (click)="getQrCode()">刷新</a>
+    </div>
   </div>
   <nz-form-item *ngIf="loginType == 'normal' || loginType == 'mobile'">
     <nz-col [nzSpan]="12">

+ 2 - 1
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.less

@@ -1,5 +1,6 @@
 @import '@delon/theme/index';
 
+
 :host {
   display: block;
   width: 368px;
@@ -81,4 +82,4 @@ input{
 
 .qrcode iframe{
   border: 0;
-}
+}

+ 52 - 1
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.ts

@@ -31,6 +31,7 @@ import { ImageCaptchaService } from '../../../service/image-captcha.service';
 import { SocialsProviderService } from '../../../service/socials-provider.service';
 import { CONSTS } from '../../../shared/consts';
 
+
 import { stringify } from 'querystring';
 
 @Component({
@@ -54,6 +55,7 @@ export class UserLoginComponent implements OnInit, OnDestroy {
   loginType = 'normal';
   loading = false;
   passwordVisible = false;
+  qrexpire = false;
   imageCaptcha = '';
   captchaType = '';
   state = '';
@@ -287,6 +289,10 @@ export class UserLoginComponent implements OnInit, OnDestroy {
   }
 
   getQrCode(): void {
+    this.qrexpire = false;
+    if (this.interval$) {
+      clearInterval(this.interval$);
+    }
     this.authnService.clearUser();
     this.socialsProviderService.scanqrcode(this.socials.qrScan).subscribe(res => {
       if (res.code === 0) {
@@ -294,11 +300,14 @@ export class UserLoginComponent implements OnInit, OnDestroy {
           this.qrScanWorkweixin(res.data);
         } else if (this.socials.qrScan === 'dingtalk') {
           this.qrScanDingtalk(res.data);
-        } else if (this.socials.qrScan === 'feishu') {
+        } else if (this.socials.qrScan === 'maxkey') {
+          this.qrScanMaxkey(res.data);
+        }else if (this.socials.qrScan === 'feishu') {
           this.qrScanFeishu(res.data);
         }
       }
     });
+
   }
 
   // #endregion
@@ -364,4 +373,46 @@ export class UserLoginComponent implements OnInit, OnDestroy {
     });
   }
   // #region QR Scan end
+
+  qrScanMaxkey(data: any) {
+    // @ts-ignore
+    document.getElementById("div_qrcodelogin").innerHTML='';
+    // @ts-ignore
+    var qrcode = new QRCode("div_qrcodelogin", {
+      width: 200,
+      height: 200,
+      colorDark : "#000000",
+      colorLight : "#ffffff"
+    }).makeCode(data.state);
+      //3分钟监听二维码
+    this.count = 90;
+    this.interval$ = setInterval(() => {
+      this.count -= 1;
+      if(this.loginType != 'qrscan') {
+        clearInterval(this.interval$);
+      }
+      if (this.count <= 0) {
+        clearInterval(this.interval$);
+      }
+      //轮询发送监听请求
+      this.socialsProviderService.qrcallback(this.socials.qrScan,data.state).subscribe(res => {
+        if (res.code === 0) {
+          // 清空路由复用信息
+          this.reuseTabService.clear();
+          // 设置用户Token信息
+          this.authnService.auth(res.data);
+          this.authnService.navigate({});
+          clearInterval(this.interval$);
+        } else if (res.code === 102) {
+          // 二维码过期
+          clearInterval(this.interval$);
+          this.qrexpire = true;
+          this.cdr.detectChanges();
+        } else if (res.code === 103) {
+          // 暂无用户扫码
+        }
+      });
+      this.cdr.detectChanges();
+    }, 2000);
+  }
 }

+ 2 - 2
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport.module.ts

@@ -17,8 +17,8 @@
 import { NgModule } from '@angular/core';
 import { SharedModule } from '@shared';
 import { NzStepsModule } from 'ng-zorro-antd/steps';
-
 import { CallbackComponent } from './callback.component';
+import { SocialsProviderBindUserComponent } from './socials-provider-bind-user/socials-provider-bind-user.component';
 import { ForgotComponent } from './forgot/forgot.component';
 import { UserLockComponent } from './lock/lock.component';
 import { UserLoginComponent } from './login/login.component';
@@ -26,7 +26,7 @@ import { PassportRoutingModule } from './passport-routing.module';
 import { UserRegisterResultComponent } from './register-result/register-result.component';
 import { UserRegisterComponent } from './register/register.component';
 
-const COMPONENTS = [UserLoginComponent, UserRegisterResultComponent, UserRegisterComponent, UserLockComponent, CallbackComponent];
+const COMPONENTS = [SocialsProviderBindUserComponent,UserLoginComponent, UserRegisterResultComponent, UserRegisterComponent, UserLockComponent, CallbackComponent];
 
 @NgModule({
   imports: [SharedModule, PassportRoutingModule, NzStepsModule],

+ 30 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.html

@@ -0,0 +1,30 @@
+<div *nzModalTitle>绑定</div>
+<div>
+  <form nz-form [formGroup]="formGroup" (ngSubmit)="onSubmit($event)" se-container="1">
+    <nz-form-item style="width: 100%">
+      <nz-form-control nzErrorTip="Please input your telephone!">
+        <nz-input-group nzSize="large" nzPrefixIcon="user">
+          <input nz-input formControlName="telephone" placeholder="{{ 'mxk.login.text.mobile' | i18n }}" />
+        </nz-input-group>
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item style="width: 100%">
+      <nz-form-control nzErrorTip="Please input your verification code!">
+        <nz-input-group nzSize="large" nzPrefixIcon="mail" nzSearch [nzAddOnAfter]="suffixSendOtpCodeButton">
+          <input nz-input formControlName="verificationCode" placeholder="{{ 'mxk.login.text.captcha' | i18n }}" />
+        </nz-input-group>
+        <ng-template #suffixSendOtpCodeButton>
+          <button type="button" nz-button nzSize="large" (click)="sendOtpCode()" [disabled]="count > 0" nzBlock [nzLoading]="loading">
+            {{ count ? count + 's' : ('app.register.get-verification-code' | i18n) }}
+          </button>
+        </ng-template>
+      </nz-form-control>
+    </nz-form-item>
+  </form>
+</div>
+
+
+<div *nzModalFooter>
+  <button nz-button nzType="default" (click)="onClose($event)">{{ 'mxk.text.close' | i18n }}</button>
+  <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.submit' | i18n }}</button>
+</div>

+ 0 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.less


+ 42 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.spec.ts

@@ -0,0 +1,42 @@
+/*
+ * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SocialsProviderBindUserComponent } from './socials-provider-bind-user.component';
+
+describe('SocialsProviderBindUserComponent', () => {
+  let component: SocialsProviderBindUserComponent;
+  let fixture: ComponentFixture<SocialsProviderBindUserComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ SocialsProviderBindUserComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SocialsProviderBindUserComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 124 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.ts

@@ -0,0 +1,124 @@
+/*
+ * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, ChangeDetectorRef, Input, OnInit, Inject } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { I18NService } from '@core';
+import { _HttpClient, ALAIN_I18N_TOKEN, SettingsService } from '@delon/theme';
+import format from 'date-fns/format';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal';
+import { AuthnService } from "../../../service/authn.service";
+import { SocialsProviderService } from "../../../service/socials-provider.service";
+import { ReuseTabService } from "@delon/abc/reuse-tab";
+
+@Component({
+  selector: 'app-socials-provider-binduser',
+  templateUrl: './socials-provider-bind-user.component.html',
+  styles: [
+    `
+      nz-form-item {
+        width: 100%;
+      }
+    `
+  ],
+  styleUrls: ['./socials-provider-bind-user.component.less']
+})
+export class SocialsProviderBindUserComponent implements OnInit {
+  @Input() socialUserId?: String;
+  @Input() provider?: String;
+  loading = false;
+  count = 0;
+  formGroup: FormGroup = new FormGroup({});
+  interval$: any;
+  constructor(
+    private modalRef: NzModalRef,
+    private fb: FormBuilder,
+    private authnService: AuthnService,
+    private msg: NzMessageService,
+    private socialsProviderService: SocialsProviderService,
+    @Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
+    @Inject(ReuseTabService)
+    private reuseTabService: ReuseTabService,
+    private cdr: ChangeDetectorRef
+  ) {}
+
+  ngOnInit(): void {
+    this.formGroup = this.fb.group({
+      telephone: [null, [Validators.required]],
+      verificationCode: [null, [Validators.required]]
+    });
+    console.log("bind open form : ",this.provider,this.socialUserId)
+  }
+
+  onClose(e: MouseEvent): void {
+    e.preventDefault();
+    this.modalRef.destroy({ refresh: false });
+  }
+
+  onSubmit(e: MouseEvent): void {
+    console.log("this.formGroup.valid",this.formGroup.valid)
+    //表单验证
+    if (this.formGroup.valid) {
+      let request = {
+        username: this.socialUserId,
+        mobile: this.formGroup.get('telephone')?.value,
+        code: this.formGroup.get('verificationCode')?.value,
+        authType: this.provider
+      }
+      this.authnService.bindSocialsUser(request).subscribe(res => {
+        if (res.code === 0) {
+          // 清空路由复用信息
+          this.reuseTabService.clear();
+          // 设置用户Token信息
+          this.authnService.auth(res.data);
+          this.authnService.navigate({});
+        } else {
+          this.msg.error(`绑定失败`);
+        }
+      });
+
+    } else {
+      Object.values(this.formGroup.controls).forEach(control => {
+        if (control.invalid) {
+          control.markAsDirty();
+          control.updateValueAndValidity({ onlySelf: true });
+        }
+      });
+    }
+
+    e.preventDefault();
+  }
+
+  sendOtpCode(): void {
+
+    this.authnService.produceOtp({ mobile:this.formGroup.get('telephone')?.value}).subscribe(res => {
+      if (res.code !== 0) {
+        this.msg.error(`发送失败`);
+      }else {
+        this.msg.success(`发送成功`);
+      }
+    });
+    this.count = 59;
+    this.interval$ = setInterval(() => {
+      this.count -= 1;
+      if (this.count <= 0) {
+        clearInterval(this.interval$);
+      }
+      this.cdr.detectChanges();
+    }, 1000);
+  }
+}

+ 4 - 0
maxkey-web-frontend/maxkey-web-app/src/app/service/authn.service.ts

@@ -61,6 +61,10 @@ export class AuthnService {
     return this.http.post('/login/signin?_allow_anonymous=true', authParam);
   }
 
+  bindSocialsUser(authParam: any) {
+    return this.http.post('/login/signin/bindusersocials?_allow_anonymous=true', authParam);
+  }
+
   //退出
   logout() {
     this.cookieService.delete(CONSTS.CONGRESS, '/');

+ 8 - 0
maxkey-web-frontend/maxkey-web-app/src/app/service/socials-provider.service.ts

@@ -43,7 +43,15 @@ export class SocialsProviderService extends BaseService<SocialsProvider> {
     return this.getByParams(params, `/logon/oauth20/callback/${provider}?_allow_anonymous=true`);
   }
 
+  bindUser(provider: string, params: NzSafeAny): Observable<Message<SocialsProvider>> {
+    return this.getByParams(params, `/logon/oauth20/binduser/${provider}?_allow_anonymous=true`);
+  }
+
   bind(provider: string, params: NzSafeAny): Observable<Message<SocialsProvider>> {
     return this.getByParams(params, `/logon/oauth20/bind/${provider}?_allow_anonymous=true`);
   }
+
+  qrcallback(provider: string, token: string ): Observable<Message<SocialsProvider>> {
+    return this.getByParams({}, `/logon/oauth20/qrcallback/${provider}/${token}?_allow_anonymous=true`);
+  }
 }

文件差异内容过多而无法显示
+ 146 - 0
maxkey-web-frontend/maxkey-web-app/src/assets/qrcode/qrcode.min.js


+ 1 - 1
maxkey-web-frontend/maxkey-web-app/src/environments/environment.ts

@@ -27,7 +27,7 @@ export const environment = {
   production: false,
   useHash: true,
   api: {
-    baseUrl: 'http://sso.maxkey.top:9527/sign/',
+    baseUrl: '/sign/',
     refreshTokenEnabled: true,
     refreshTokenType: 're-request'
   },

+ 4 - 3
maxkey-web-frontend/maxkey-web-app/src/index.html

@@ -27,6 +27,7 @@
   <meta http-equiv="description" content="MaxKey Single Sign-On">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
   <script src="./assets/transform.js"></script>
+  <script src="./assets/qrcode/qrcode.min.js"></script>
   <!-- Apple Touch Icon -->
   <!-- <link rel="apple-touch-icon" href="custom-icon.png"> -->
   <style type="text/css">
@@ -142,9 +143,9 @@
   </div>
 </body>
 <!--attention http or https-->
-<!--企业微信
+--企业微信
 <script src="http://wwcdn.weixin.qq.com/node/wework/wwopen/js/wwLogin-1.2.7.js"></script>
--->
+
 <!--钉钉-->
 <!---->
 <script src="http://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
@@ -191,4 +192,4 @@
 </script>
 -->
 
-</html>
+</html>

部分文件因为文件数量过多而无法显示