ตรรกะการเข้าสู่ระบบ Angular Frontend

Oct 13 2020

นี่คือการติดตามคำถามนี้Node.js ตรรกะแบ็กเอนด์เข้าสู่ระบบ ฉันเขียนตรรกะส่วนหน้าเชิงมุมสำหรับล็อกอินต่อไปนี้สำหรับแบ็กเอนด์ Node.js ของฉัน (ดูคำถามก่อนหน้านี้ด้านบน) การรักษาความปลอดภัยประสิทธิภาพการสร้าง async / sync การบันทึกเป็นสิ่งที่ดีหรือไม่? ความปลอดภัยเป็นปัญหาหลักของฉัน ในรูปแบบที่สวยกว่าคำถามคือ:

  • การรักษาความปลอดภัย : เว็บไซต์ของฉันปลอดภัยในลักษณะรูปร่างหรือรูปแบบใด ๆ หรือไม่? ผมสงสัยว่าถ้าผมสามารถใช้มาตรการรักษาความปลอดภัยอื่น ๆ Angularกว่าคนที่มีการสร้างขึ้นในวิธีการที่ให้บริการโดย การส่งรหัสผ่านเป็นข้อความธรรมดาไม่ใช่ปัญหาด้านความปลอดภัยใช่หรือไม่? แล้ว XSS และปัญหาที่คล้ายกันล่ะ? การเข้าสู่ระบบของฉันไม่สามารถหลีกเลี่ยงได้หรือไม่? นั่นจะเป็นความผิดพลาดอย่างร้ายแรง
  • ประสิทธิภาพ : การตรวจสอบชื่อผู้ใช้และรหัสผ่านมีประสิทธิภาพอย่างไร มีวิธีไหนที่ดีกว่านี้ไหม?
  • BUILDING : ฉันสามารถโหลดเว็บไซต์ได้อย่างไร?
  • ASYNC / SYNC : ฉันรู้ว่าฉัน preform asyncและsyncโทรในเวลาเดียวกัน มีปัญหาอะไรไหม?
  • การบันทึก : ฉันบันทึกการเชื่อมต่อทั้งหมดไปยังเซิร์ฟเวอร์และความพยายามในการเข้าสู่ระบบทั้งหมด นี่เป็นแนวทางปฏิบัติที่ดีหรือฉันทำมากเกินไปในสิ่งที่ควรจะทำให้สำเร็จ
  • MISC : มีข้อผิดพลาดในการเล่นระหว่างแบ็กเอนด์และส่วนหน้าหรือไม่? หากฉันลืมประเด็นสำคัญอื่น ๆ เกี่ยวกับรหัสฉันจะดีใจถ้าคุณพูดถึงพวกเขาด้วย (ที่มา: Login Server with Node.js )

รหัสของฉัน :

authentication.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { environment } from '../../environments/environment';
import { User } from '../models/user.model';
import { Router } from '@angular/router';
import { GlobalDataService } from './global-data.service';

@Injectable({ providedIn: 'root' })
export class AuthenticationService {
    constructor(private http: HttpClient,
                private router: Router, public DataService: GlobalDataService) {
        this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
        this.currentUser = this.currentUserSubject.asObservable();
        this.LoggedIn = true;
    }
    public LoggedIn = true;
    public get currentUserValue(): User {
        return this.currentUserSubject.value;
    }
    private currentUserSubject: BehaviorSubject<User>;
    public currentUser: Observable<User>;
  getRedirectUrl() {
    throw new Error('Method not implemented.');
  }
  isUserLoggedIn() {
    throw new Error('Method not implemented.');
  }

  login(email: string, password: string) {
    return this.http.post<any>(`${environment.apiUrl}/api/login`, { email, password }, {withCredentials: true})
        .pipe(map(user => {
            // login successful if there's a jwt token in the response
            if (user && user.token) {

                // store user details and jwt token in local storage to keep user logged in between page refreshes
                // https://dev.to/rdegges/please-stop-using-local-storage-1i04
                localStorage.setItem('currentUserToken', JSON.stringify(user));
                this.currentUserSubject.next(user);
            }
            // set firstname & email of loggedin user
            this.DataService.loggedinfirstname = user['firstname'];
            this.DataService.loggedinemail = user['eMail'];
            this.redirtoDashboard();
            this.Toolbar();
            this.DataService.prefillSenderData();
            return user;
        }));
  }

  redirtoDashboard() {
      this.router.navigate(['order']);
  }

  Toolbar() {
      this.LoggedIn = !this.LoggedIn;
  }
}

login.component.ts:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';

import { AuthenticationService } from '../services/authentication.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  returnUrl: string;
  loginForm: FormGroup;
  submitted = false;
  error = '';
  loading = false;
  public errorMsg = 'Please login to continue.';
  public redirected: boolean;
  public utm_source: string;

  constructor(private router: Router, private formBuilder: FormBuilder,
              private authenticationService: AuthenticationService, private activatedRoute: ActivatedRoute) {
      if (this.authenticationService.currentUserValue) {
        this.router.navigate(['order']);
    }
      this.activatedRoute.queryParams.subscribe(params => {
      const param = params['utm_source'];

      if (param === 'order' || param === 'work-document' || param === 'profile') {
        this.redirected = true;
        this.utm_source = param;
      } else {
        this.redirected = false;
      }
  });
  }

  ngOnInit(): void {
    this.loginForm = this.formBuilder.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]]
  });
  }

// convenience getter for easy access to form fields
get f() { return this.loginForm.controls; }

  onSubmit(loginsubmit) {
    this.submitted = true;
    // stop here if form is invalid
    if (this.loginForm.invalid) {
        return console.log('LoginForm Invalid');
    }
    this.loading = true;
    this.authenticationService.login(this.f.email.value, this.f.password.value)
        .pipe(first())
        .subscribe(
            data => {
                if (this.redirected) {
                  this.router.navigate([this.utm_source]);
                } else {
                  this.router.navigate(['order']);
                }

            },
            error => {
                console.log('Login->authservice->err: ', error);
                this.error = error;
                this.loading = false;
            });
}

}

login.component.html:

<div class="container">
  <div class="row">
    <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
      <div class="card card-signin my-5">
        <div class="card-body">
          <h5 class="card-title text-center">Login</h5>
          <br>
            <form [formGroup]="loginForm" class="form-signin" (ngSubmit)="onSubmit(this.loginForm.value)">
              <div class="form-label-group">
                <input #userName formControlName="email" type="text" id="inputUser" class="form-control" placeholder="E-Mail" required autofocus [ngClass]="{ 'is-invalid': submitted && f.email.errors }">
                  <div *ngIf="submitted && f['email'].errors" class="invalid-feedback">
                    <div *ngIf="f['email'].errors.required">E-Mail is required</div>
                  </div>
                </div>
                <br>
                  <div class="form-label-group">
                    <input #password type="password" formControlName="password" id="inputPassword" class="form-control" placeholder="Password" required [ngClass]="{ 'is-invalid': submitted && f.password.errors }">
                      <div *ngIf="submitted && f['password'].errors" class="invalid-feedback">
                        <div *ngIf="f['password'].errors.required">Password is required</div>
                      </div>
                    </div>
                    <br>
                      <div *ngIf="redirected">
                        <mat-error>
                          <p class="alert alert-danger">
                            {{errorMsg}}
                          </p>
                        </mat-error>
                      </div>
                      <button [disabled]="!loginForm.valid" class="btn btn-dark btn-block" id="loginSubmit" type="submit">Login</button>
                      <div class="forgot-password-link">
                        <a routerLink="/forgot-password">Forgot password</a>
                      </div>
                    </form>
                  </div>
                </div>
              </div>
            </div>
          </div>

คำตอบ

5 JanRecker Oct 21 2020 at 19:35

ขออภัยฉันไม่ได้ดูโค้ดแบ็กเอนด์ของคุณดังนั้นนี่เป็นเพียงครึ่งรีวิว

สำหรับคำถามของคุณ:

  • ความปลอดภัย
    ตามที่ Sam วิเคราะห์แล้วไม่มีปัญหาที่ชัดเจนในส่วนของส่วนหน้า (ตราบใดที่คุณใช้ HTTPS) ฉันคาดว่ารหัสผ่านถูกแฮช (ด้วยเกลือ) ในแบ็กเอนด์และมีเพียงแฮชเท่านั้นที่ถูกเก็บไว้ในฐานข้อมูลดังนั้นจึงไม่มีใครสามารถดึงรหัสผ่านจริงออกจากฐานข้อมูลได้

  • ประสิทธิภาพ
    ใช่ผู้ตรวจสอบความถูกต้องเป็นวิธีที่จะไปในเชิงมุม ในทางเทคนิคจะมีประสิทธิภาพมากกว่าเล็กน้อยในการใช้การตรวจสอบความถูกต้อง HTML แต่แฟร็กทัลของมิลลิวินาทีนั้นไม่คุ้มค่าที่จะสูญเสียความยืดหยุ่นของ Validators ไป

  • อาคาร

  • Async / Sync
    ปัญหาในการเรียกเมธอด async โดยไม่จัดการกับผลลัพธ์คือคุณจะไม่รู้ว่ามีบางอย่างไม่ได้ผลตามที่คาดไว้ ไม่เป็นไรถ้าคุณรู้ว่าโค้ดที่คุณใช้นั้นได้รับการพัฒนาและดูแลโดยนักพัฒนาที่เก่งกาจซึ่งอยู่เหนือข้อผิดพลาด หากนักพัฒนาเป็นมนุษย์คุณควรคาดหวังไว้เสมอว่าอาจมีปัญหา และหากคุณเรียกใช้วิธีการและคุณรู้ว่าปัญหาใด ๆ ที่ไม่มีปัญหาสำหรับรหัสของคุณให้กำหนดรหัสของคุณให้ชัดเจนเพื่อให้นักพัฒนาต่อไปนี้ (เช่นคุณในอีก 3 เดือน) จะรู้เช่นกัน :-)

  • การบันทึก
    คำถามคือสิ่งที่คุณตั้งใจกับการบันทึก หากคุณต้องการตรวจสอบทุกสิ่งเพื่อรับความรู้เกี่ยวกับผู้ใช้โครงสร้างพื้นฐานของคุณ ฯลฯ ยิ่งไปกว่านั้นก็ดี เอาล่ะฉันจะใช้หนึ่งในกรอบที่มีอยู่เพื่อทำสิ่งนั้นให้ฉันและไม่สร้างวงล้อขึ้นมาใหม่
    หากคุณสนใจ แต่สิ่งที่ไม่ดีฉันจะบันทึกเฉพาะสิ่งเหล่านั้น (เช่นการพยายามเข้าสู่ระบบล้มเหลว)
    เช่นเคยไม่มี "ใช่" หรือ "ไม่" มันขึ้นอยู่กับความตั้งใจของคุณ
    ตามคำชี้แจงconsole.logไม่ใช่ "การบันทึก" สำหรับฉันเพราะผู้ใช้เท่านั้นที่มองเห็นได้และสำหรับเขาก็ต่อเมื่อเขาเปิดคอนโซลไว้

  • MISC
    ดูสิ่งต่อไปนี้

Refactoring

ก่อนอื่นฉันต้องการ refactor โค้ดเล็กน้อยเพื่อให้อ่านง่าย จากประสบการณ์ของฉันมันช่วยให้มองเห็นข้อผิดพลาดได้ง่ายขึ้น คุณสามารถเพิกเฉยและข้ามไปยังส่วนที่น่าสนใจได้หากต้องการ

โดยทั่วไปฉันชอบที่จะใช้เสมอprivateและpublicจำกัด ให้มากที่สุด แสดงให้ผู้อ่านเห็นว่าฉันคิดเกี่ยวกับขอบเขตของวิธีการ / ตัวแปร และช่วยลดโอกาสในการใช้ผิดประเภท privateถ้าผมไม่แน่ใจว่าแล้วฉันเริ่มต้นด้วย
หากไม่มีการใช้งานจะเป็นสาธารณะโดยค่าเริ่มต้น และในฐานะผู้อ่านฉันไม่รู้ว่าผู้พัฒนาเลือกสิ่งนั้นโดยตั้งใจหรือลืมไป

AuthenticationService

LoggedInถูกตั้งค่าเป็นจริงในเวลานิยาม ( public LoggedIn = true) และอีกครั้งในตัวสร้าง โดยส่วนตัวแล้วฉันชอบกำหนดค่าเริ่มต้นในเวลาที่กำหนด นอกจากนี้ค่านี้ยังใช้เป็น "true หมายถึงไม่ได้ล็อกอิน" นั่นเป็นเรื่องที่น่ารำคาญดังนั้นฉันจะเปลี่ยนชื่อisLogedInและเริ่มต้นด้วย false

DataService เป็นสาธารณะ แต่ดูเหมือนจะไม่ถูกใช้นอกชั้นเรียน

ฉันไม่ชอบสตริงคงที่ในโค้ดของฉันดังนั้นฉันจึงแยกมันออกเป็นค่าคงที่ ชอบ
private loginUrl: string = `${environment.apiUrl}/api/login`;

ฉันชอบใช้ตัวดำเนินการ RxJs "พูด" mapวิธีในloginไม่เปลี่ยนกระแสดังนั้นผมจะใช้tapแทน นั่นทำให้เห็นได้ชัดว่ามีเพียงผลข้างเคียง `นอกจากนี้เรายังสามารถข้ามreturnบรรทัดได้

ฉันชอบรหัสเร่งความเร็ว ดังนั้นถ้าฉันสามารถแยกโค้ดบางบรรทัดออกเป็นวิธีการพูดได้ฉันกำลังทำเช่นนั้นเพราะฉันต้องอ่านชื่อวิธีการเท่านั้นไม่ใช่โค้ดทั้งหมดที่อยู่ข้างหลังและสามารถตัดสินใจได้ว่าฉันต้องการดำน้ำลึกลงไปหรือเพียงแค่ดำเนินการต่อ
ดังนั้นฉันจะเปลี่ยนรหัสในmapของloginวิธีบิต

นอกจากนี้ดูเหมือนว่าเมธอด "Toolbar" จะใช้เพื่อเปลี่ยนLogedInสถานะเพียงครั้งเดียวเท่านั้น เราจึงสามารถกำหนดค่าโดยไม่ต้องสลับ
นอกจากนี้ยังมีปัญหาที่นี่ (ดูในบทปัญหาในภายหลัง) ดังนั้นฉันจะย้ายการเรียกใช้ฟังก์ชันนั้นไปยังคำสั่ง if

โดยปกติการเปลี่ยนเส้นทางเป็นสิ่งสุดท้ายที่ควรเกิดขึ้นดังนั้นฉันจึงย้ายไปที่ส่วนท้ายของการแตะ

export class AuthenticationService {
    public isLoggedIn = false;
    public currentUser: Observable<User>

    public get currentUserValue(): User {
        return this.currentUserSubject.value;
    }    
    
    private currentUserSubject: BehaviorSubject<User> = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
    private loginUrl: string = `${environment.apiUrl}/api/login`;

    constructor(private DataService: GlobalDataService,
                private http: HttpClient,
                private router: Router) {
        this.currentUser = this.currentUserSubject.asObservable();
    }
       
  private getRedirectUrl() {
    throw new Error('Method not implemented.');
  }
  
  private isUserLoggedIn() {
    throw new Error('Method not implemented.');
  }

  public login(email: string, password: string):Observable<User> {
    return this.http.post<User>(loginUrl, { email, password }, {withCredentials: true})
        .pipe(
            tap(user => {
            // login successful if there's a jwt token in the response
            if (this.isLoginSuccessful(user)) {
                this.setLogedInUser(user);
                this.isLoggedIn = true;
            }
            this.setDataServiceForUser(user);            
            this.toggleLoginStatus();
            this.redirectToDashboard();
        })
    );
  }

    private isLoginSuccessful(user:User):boolean{
        return user && user.token;
    }

  private redirectToDashboard():void {
      this.router.navigate(['order']);
  }

    private setLogedInUser(user: User):void{
        // store user details and jwt token in local storage to keep user logged in between page refreshes
        // https://dev.to/rdegges/please-stop-using-local-storage-1i04
        localStorage.setItem('currentUserToken', JSON.stringify(user));
        this.currentUserSubject.next(user);
    }

    private setDataServiceForUser(user:User):void{
        // set firstname & email of loggedin user
        this.DataService.loggedinfirstname = user['firstname'];
        this.DataService.loggedinemail = user['eMail'];
        this.DataService.prefillSenderData();
    }
}

เข้าสู่ระบบส่วนประกอบ

ในonSubmitคุณไม่จำเป็นต้องมีfirst(). ด้านหลังthis.authenticationService.loginคือคำขอ http และสิ่งเหล่านั้นจะสิ้นสุดโดยอัตโนมัติหลังจากผลลัพธ์แรก ด้วยเหตุผลเดียวกันคุณจึงไม่จำเป็นต้องยกเลิกการสมัครสมาชิกเหล่านั้น

ปัญหา

AuthenticationService

ในloginวิธีนี้ดูเหมือนว่าแม้ในกรณีของการเข้าสู่ระบบที่ไม่สำเร็จ (ไม่มีผู้ใช้หรือไม่มีข้อมูลโทเค็น) ก็ยังพยายามส่งข้อมูลไปDataServiceยังสิ่งต่างๆ ที่นี่ฉันจะแยกสิ่งเหล่านี้ออกจากกันอย่างชัดเจนว่าควรเกิดขึ้นหลังจากพยายามเข้าสู่ระบบและสิ่งที่อาจเกิดขึ้นหลังจากการเข้าสู่ระบบสำเร็จเท่านั้น
โดยเฉพาะอย่างยิ่งตอนนี้จะเปลี่ยนไปLogedInแม้ว่าการเข้าสู่ระบบจะไม่สำเร็จก็ตาม

เข้าสู่ระบบส่วนประกอบ

ปฏิบัติที่ดีที่สุด

แนวทางปฏิบัติที่ดีที่สุด (อย่างน้อยก็ในสายตาของฉัน :-))

ในรหัสข้อมูลผู้ใช้ใช้สำหรับสองสิ่ง สำหรับข้อมูลโดยละเอียดเกี่ยวกับผู้ใช้และประการที่สองเป็น "ผู้ใช้ล็อกอิน" โดยปริยาย ใน AuthentificationService การเชื่อมต่อนี้ถูกต้อง แต่ภายนอกฉันจะให้ข้อมูลผู้ใช้และข้อมูล "isLogedIn" เพิ่มเติม ด้วยวิธีนี้นักพัฒนาไม่จำเป็นต้อง "รู้" ว่าข้อมูลผู้ใช้บ่งบอกเป็นนัยว่าผู้ใช้เข้าสู่ระบบ

เป็นนิสัยที่ดีในการยกเลิกการสมัครเมื่อคุณออกจากองค์ประกอบ ดังนั้นฉันมักจะทำอะไรแบบนี้

private subscriptions: Subscription() = new Subscription();
...
this.subscriptions.add(
    sourceA.subscribe(...)
)
this.subscriptions.add(
    sourceB.subscribe(...)
)

ngOnDestroy(){
    this.subscriptions.unsubcribe();
}

ด้วยวิธีนี้ทันทีที่คอมโพเนนต์ถูกทำลายการสมัครสมาชิกเหล่านั้นทั้งหมดจะถูกยกเลิกโดยอัตโนมัติ โปรดทราบว่าส่วนประกอบจะถูกทำลายก็ต่อเมื่อถูกลบออกจาก DOM อย่างสมบูรณ์ ถ้ามันซ่อนอยู่แสดงว่ามันยังมีชีวิตอยู่

ฉันหวังว่าหนึ่งหรือสองสิ่งจะเป็นประโยชน์สำหรับคุณ

ขอแสดงความนับถือ

2 SᴀᴍOnᴇᴌᴀ Oct 21 2020 at 12:27

คำนำ

ฉันใช้ AngularJS เมื่อสองสามปีก่อน แต่ไม่ได้เข้าสู่ Angular2 + ดังนั้นความรู้ของฉันเกี่ยวกับมันจึงบางเฉียบ อย่างไรก็ตามฉันมีความคุ้นเคยกับ Javascript และเฟรมเวิร์กต่างๆพอสมควร

การตอบคำถาม

ความปลอดภัย การส่งรหัสผ่านเป็นข้อความธรรมดาไม่ใช่ปัญหาด้านความปลอดภัยใช่หรือไม่?

ฉันพบโพสต์เกี่ยวกับคำถามนี้ในเว็บไซต์ SE หลายแห่ง ตัวอย่างเช่นฉันพบว่าสามารถส่งรหัสผ่านข้อความธรรมดาผ่าน HTTPS ได้หรือไม่ [ซ้ำ] หากต้องการอ้างอิงคำตอบที่ยอมรับโดย Buffalo5ix :

เป็นแนวทางปฏิบัติมาตรฐานในการส่งรหัสผ่าน "ข้อความธรรมดา" ผ่าน HTTPS ในที่สุดรหัสผ่านจะไม่ใช่ข้อความธรรมดาเนื่องจากการสื่อสารไคลเอนต์ - เซิร์ฟเวอร์จะถูกเข้ารหัสตาม TLS

คำถามนั้นถูกทำเครื่องหมายว่าซ้ำกับโพสต์อื่นสองโพสต์ซึ่งรวมถึงคำถามนี้ฉันเพิ่งส่งชื่อผู้ใช้และรหัสผ่านผ่าน https นี่โอเคไหม? . มีสองคำตอบและคำตอบที่สองโดย Steveเสนอตัวเลือก:

สิ่งที่คุณทำได้เพิ่มเติมอีกอย่างหนึ่งคือการใช้ใบรับรองไคลเอ็นต์ เซิร์ฟเวอร์สามารถรับประกันตัวเองได้ว่าไม่มี MitM โดยต้องมีใบรับรองไคลเอ็นต์ มิฉะนั้นเขาจะต้องไว้วางใจลูกค้าในการตรวจสอบอย่างถูกต้องว่าไม่มี MitM นี่เป็นมากกว่าบริการมากมายที่ควรเต็มใจที่จะไว้วางใจ

ฉันไม่เคยได้ยินว่ามีใครทำแบบนั้น แต่บางทีทำเสร็จแล้วและเราก็ไม่รู้เรื่องนี้

แม้จะมีคำถาม Stack Overflow เกี่ยวกับคำถามโดยคำตอบที่ยอมรับนั้นคล้ายคลึงกับคำตอบที่ยอมรับของคำถามแรก (จาก Security SE) ที่กล่าวถึงข้างต้น

EFFICIENCYการตรวจสอบชื่อผู้ใช้และรหัสผ่านมีประสิทธิภาพอย่างไร มีวิธีไหนที่ดีกว่านี้ไหม?

ฉันไม่รู้วิธีที่ดีกว่าในการทำสิ่งนี้ แต่ฉันสังเกตเห็นAuthenticationService.login()ว่าบรรทัดเหล่านี้ควรจะสามารถใช้สัญกรณ์จุด:

 this.DataService.loggedinfirstname = user['firstname'];
 this.DataService.loggedinemail = user['eMail'];

ชื่อวิธีการ

ฉันถามเกี่ยวกับการอัปเดตคุณสมบัติLoggedInและสังเกตว่ามีการตั้งค่าในตัวสร้างและแก้ไขในวิธีการToolbarแล้ว ชื่อที่Toolbarดูเหมือนว่าอาจเกี่ยวข้องกับการดึงแถบเครื่องมือ วิธีการอื่น ๆ ในระดับที่มีคำกริยา - เช่นการเข้าสู่ระบบ , redirtoDashboard ชื่อวิธีการที่เหมาะสมกว่าสำหรับวิธีการนั้นอาจเป็นToggleLoggedInหรือบางอย่างตามบรรทัดเหล่านั้น

ลดความซับซ้อนหรือเงื่อนไข

บรรทัดนี้ในLoginComponent::constructor():

       if (param === 'order' || param === 'work-document' || param === 'profile') {

สามารถทำให้ง่ายขึ้นโดยใช้Array.prototype.includes()ซึ่งทำการเปรียบเทียบอย่างเข้มงวด1 2 :

  if ([ 'order', 'work-document', 'profile'].includes(param)) {