[Tự làm] Đăng nhập nhanh tài khoản thành viên (không chuyển trang)

  Bài viết hay nhất1
Thật ra đoạn kịch bản dưới đây cũng không có gì là mới mẻ, nhưng mình nghĩ sẽ có một số bạn đến nó.

Cập nhật
16:15 25/01/2016

Giới thiệu
Sử dụng xhr để gửi yêu cầu đăng nhập tới máy chủ. Nó giúp tiết kiệm băng thông, dữ liệu và thời gian tải trang vì nhận thông tin qua xhr.

Lời khuyên
Nên sử dụng cho các ứng dụng đăng nhập nhanh (dạng thẻ nhỏ). Ví dụ: diễn đàn devs.

Hạn chế
Mã nguồn diễn đàn không hỗ trợ API nên việc này mình phải sử lí thủ công.
Mình chưa tìm cách đăng nhập rõ nguyên lí nên việc đăng nhập chỉ có thể sử dụng ở trang người dùng (blank page) hoặc trên chính diễn đàn cần đăng nhập.

Lưu ý
Phần kiểm tra trước khi gửi yêu cầu, vì mình không biết cách giới hạn tên người dùng và mật khẩu nên mình để tạm như vậy, khi bạn bạn nào gé qua góp ý cho mình sửa.

Kịch bản
Code:
var _lh = {};
_lh.requestlogin = function(url, data, start, callback) {
    var url = typeof url == 'string' ? url : null,
        data = typeof data == 'object' ? data : null;
        start = typeof start == 'function' ? start : function() {},
       callback = typeof callback == 'function' ? callback : function() {};
    if ((typeof data.username != 'string' && typeof data.username != 'number') || (typeof data.password != 'string' && typeof data.password != 'number')) {
        callback(false, null, null);
        return false;
    } else if (!(/^([a-zA-Z0-9\_\.]{6,})$/.test(data.username)) || !(/^([\S\s]{6,})$/.test(data.password))) {
        callback(false, null, null);
        return false;
    }
    data.remember = typeof data.remember == 'number' && (/^(0|1)$/).test(data.remember) ? data.remember : 0;
    var input = {
        'username': data.username,
        'password': data.password,
        'autologin': data.remember,
        'login': ''
    };
    var data = new FormData();
    for (var label in input) {
        var value = input[label];
        data.append(label, value);
    }
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            if (xhr.status == 200 && xhr.responseText != null && new RegExp(!/<[a-z][\s\S]*>/i).test(xhr.responseText)) {
                var objMatch = ((xhr.responseText).replace(/((\s+)|(\n+))/g, '')).match(/(((if\(typeof\(_userdata\)\=\="undefined"\)var\_userdata\=newObject\(\)\;\_userdata\["session_logged_in"\]\=)(0|1)(\;))(\_userdata\["username"\]\=")([a-zA-Z0-9]+)("\;)(\_userdata\["user\_id"\]\=)([0-9\-]+)(\;))/i);
                var isLogin = typeof objMatch == 'object' && typeof objMatch[4] == 'string' ? Number(objMatch[4]) : null;
                var userName = typeof objMatch == 'object' && typeof objMatch[7] == 'string' ? objMatch[7] : null;
                var userId = typeof objMatch == 'object' && typeof objMatch[10] == 'string' ? Number(objMatch[10]) : null;
                callback(true, {
                    'login': isLogin,
                    'username': userName,
                    'userid': userId
                }, xhr);
            }
        }
    };
    xhr.open("POST", url + '/login', true);
    xhr.send(data);
    start(xhr);
};
Chèn đoạn mã này vào vùng hiển thị cho người dùng chưa đăng nhập.

Sử dụng
Code:
_lh.requestlogin('đường dẫn diễn đàn - trang chính', {
    'username': 'tên đăng nhập',
    'password': 'nhập khẩu'
}, function (xhr) { // gọi lại lúc vừa gửi.
   // xhr: dữ liệu về yêu cầu
}, function(returnValue, data, xhr) {  // gọi lại lúc kết thúc.
    // returnValue: giá trị trả về (true: hoàn thành, false: không hoàn thành)
    // data: thông tin người dùng (login: trạng thái đăng nhập, username: tên người dùng, userid: id người dùng)
    // xhr: dữ liệu về yêu cầu
});

Ứng dụng
Code:
đang cập nhật

Mình đã thử chạy trên diễn đàn Devs và một số diễn đàn khác, đã thành công.
Nếu gặp vấn đề về kịch bản, vui lòng để lại lời nhắn.
  Bài viết hay nhất2
Không biết bạn nghĩ sao chứ theo mình, việc đăng nhập dùng ajax thế này là không nên.

1. Hơi khó hiểu cho người chưa biết gì về js để có thể sử dụng được code của bạn.
2. Việc tải lại trang khi đăng nhập. Mục đích là để nhận diện dữ liệu của thành viên. Ví dụ: Bạn là một khách khi xem bài viết hoặc chuyên mục bắt buộc phải làm thành viên mới cho phép xem. Thì khi đó bạn dùng code này để đăng nhập mà không tải lại trang đó, thì dù bạn có đăng nhập bằng ajax rồi thì cũng sẽ không xem được chuyên mục hoặc bài viết đó. Vì thế code trên là vô dụng. Tương tự với chatbox cũng thế.
3. Code quá dài dòng trong khi bạn chỉ cần thêm thẻ
Code:
<input type="hidden" name="redirect" value="(Đường dẫn trang muốn chuyển khi đăng nhập. Nếu dùng js thì thêm location.href để tải lại trang hiện tại.)" />

Là có thệ giải quyết được các vấn đề ở trên rồi.
4. Việc tải trang bằng ajax hạn chế băng thông thì mình không chắc lắm. Nhưng hầu hết các tài nguyên hiện tại đều lưu vào cache cả do đó việc tải một trang cũng chả mất bao nhiêu thời gian đâu. Với lại server của fm theo mình biết thì vấn đề băng thông không tòn tại như các hosting và server khác nên vấn đề này khỏi quan tâm đến nó cũng được.

Đây chỉ là góp ý của cá nhân mình thôi. Hi vọng bạn có thể đóng góp thêm cho devs những code và tut mới. Thân chào bạn.
  Bài viết hay nhất3
[You must be registered and logged in to see this link.]
[You must be registered and logged in to see this link.] wrote:Không biết bạn nghĩ sao chứ theo mình, việc đăng nhập dùng ajax thế này là không nên.

1. Hơi khó hiểu cho người chưa biết gì về js để có thể sử dụng được code của bạn.
2. Việc tải lại trang khi đăng nhập. Mục đích là để nhận diện dữ liệu của thành viên. Ví dụ: Bạn là một khách khi xem bài viết hoặc chuyên mục bắt buộc phải làm thành viên mới cho phép xem. Thì khi đó bạn dùng code này để đăng nhập mà không tải lại trang đó, thì dù bạn có đăng nhập bằng ajax rồi thì cũng sẽ không xem được chuyên mục hoặc bài viết đó. Vì thế code trên là vô dụng. Tương tự với chatbox cũng thế.
3. Code quá dài dòng trong khi bạn chỉ cần thêm thẻ
Code:
<input type="hidden" name="redirect" value="(Đường dẫn trang muốn chuyển khi đăng nhập. Nếu dùng js thì thêm location.href để tải lại trang hiện tại.)" />

Là có thệ giải quyết được các vấn đề ở trên rồi.
4. Việc tải trang bằng ajax hạn chế băng thông thì mình không chắc lắm. Nhưng hầu hết các tài nguyên hiện tại đều lưu vào cache cả do đó việc tải một trang cũng chả mất bao nhiêu thời gian đâu. Với lại server của fm theo mình biết thì vấn đề băng thông không tòn tại như các hosting và server khác nên vấn đề này khỏi quan tâm đến nó cũng được.

Đây chỉ là góp ý của cá nhân mình thôi. Hi vọng bạn có thể đóng góp thêm cho devs những code và tut mới. Thân chào bạn.
Mình hiểu ý bạn, cảm ơn vì lời góp ý của bạn.
Mình có vài lời giải thích ngắn, hi vọng được bạn góp ý tiếp:
1. Đoạn mã ví dụ của mình như vậy là tối giản nhất có thể, mình thấy nó đơn giản hơn việc đọc 1 document js khác, nhưng có lẻ mình sẽ tối giản hơn cho lần sau.
2 - 3. Sau khi kiểm tra, nếu đăng nhập thành công bạn có thể reload trang hoặc chuyển hướng với một đoạn mã ngắn:
Code:
_lh.requestlogin('đường dẫn diễn đàn - trang chính', {
    'username': 'tên đăng nhập',
    'password': 'nhập khẩu'
}, function(returnValue, data, xhr) {
 if (returnValue == true) {
 if (data.login == true) {
 window.location.href = "đường dẫn cần đến";
 }
 }
});
4. Việc tiết kiệm thì mình tạm giải thích như sau:
Khi tải một trang web
b1: tải nội dung trang.
b2: tải các tệp nguồn (css, js) dù có cached nhưng trình duyệt vẫn phải gửi request, cached hay không thì vẫn tốn một số lượng băng thông.
b3: thực thi mã lệnh (css, js, html).
Còn việc sử dụng xhr thì chỉ cần b1, loại bỏ 2 bước còn lại. Giảm thời gian tải trang nếu người dùng đăng nhập sai (nếu đăng nhập sai thì cứ tải lại tải lại hoài thì đây là điều không nên, trong UX - trãi nghiệm người dùng).

Cảm ơn lời góp ý, mình sẽ sớm khắc phục hạn chế!
  Bài viết hay nhất4
Khi đăng nhập thì có 4 trường hợp, trừ lúc đăng nhập thành công thì thông báo lỗi sẽ được ghi trong div.msg (Invision).

  1. Thành công. Có nhiều dấu hiệu để nhận biết, một trong số đó là có nút Thoát, xét .indexOf('id="logout"') là nhanh nhất.
  2. Sai username và password. Trường hợp này thông dụng nhất nên cứ xét vào trường hợp cuối cùng.
  3. Sai username và password quá 10 lần,  với dấu hiệu Tiếng Việt Số lần đăng nhập 10 đã hết hoặc Tiếng Anh The maximum number of 10 login attempts has been exceeded. Tùy theo ngôn ngữ mặc định của diễn đàn mà xét.
  4. Bị cấm truy cập (banned), với dấu hiệu Tiếng Việt Bạn đã bị cấm truy cập diễn đàn này hoặc Tiếng Anh You have been banned from this forum. Nếu có chữ Until là cấm có thời hạn, lấy ra thời hạn bằng .match(/Until\s([\d\/]+)/)[1], không có là cấm vĩnh viễn. Nếu cấm có lý do sẽ có chữ For the reason, lấy nội dung bằng .match(/For\sthe\sreason\s:\s(.+)$/)[1]

Vì dùng ajax nên khi đăng nhập thành công thì tải lại trang là được, chạy hàm location.reload().

Ví dụ với jQuery trên phiên bản Invision, ngôn ngữ mặc định là Tiếng Anh:
Code:
var $form = $(".form-login"), // Form đăng nhập
    $input = $form.find("input");

$form.submit(function(e) {
    e.preventDefault();

    $.post("/login", $form.serialize() + "&login=ok").done(function(responseText) {

        if (responseText.indexOf('id="logout"') !== -1) {
            // Đăng nhập thành công
            location.reload();
            return false;
        }

        responseText = responseText.replace(/<img\b[^>]*>/ig, ""); // Xóa ảnh trong mã nguồn để chặn tải khi dùng $(responseText)
        var msg = $(responseText).find(".msg").text(); // Lấy nội dung thông báo khi đăng nhập không thành công

        if (msg.indexOf("login attempts has been exceeded") !== -1) {
            // Xử lý khi đăng nhập sai liên tiếp 10 lần, hệ thống sẽ chặn trong 90 phút
            return false;
        }

        if (msg.indexOf("You have been banned from this forum") !== -1) {
            // Xử lý khi bị cấm truy cập
            // Lấy giới hạn thời gian cấm bằng .match(/Until\s([\d\/]+)/)[1] nếu có
            // Lấy lý do cấm bằng .match(/For\sthe\sreason\s:\s(.+)$/)[1] nếu có
            return false;
        }

        // Xử lý khi sai thông tin đăng nhập
        $input.prop("disabled", false);
      
    }).fail(function() {
        // Xử lý khi gặp lỗi kết nối đến máy chủ
    });

    $input.prop("disabled", true);
});
Để tăng tương tác với người dùng thì nên tạo thêm 1 nơi hiển thị các thông báo về tình trạng đăng nhập và bộ đếm lùi số lần đăng nhập sai.
  Bài viết hay nhất5
Có thể cho em biết code của thớt dùng để làm gì không?
  Bài viết hay nhất6
[You must be registered and logged in to see this link.]
[You must be registered and logged in to see this link.] wrote:Có thể cho em biết code của thớt dùng để làm gì không?
Là đoạn kịch bản để thực hiện đăng nhập vào tài khoản thành viên qua xhr đấy bạn.
  Bài viết hay nhất7
You cannot reply to topics in this forum