Skip to content
Toggle navigation
Projects
Groups
Snippets
Help
aa.gusti
/
opensipkd-base
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Settings
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit caf8c1c8
authored
Mar 17, 2026
by
aa.gustiana@gmail.com
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
perbaikan validasi login untuk mencegah percobaan login berlebihan
1 parent
febbaac4
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
48 additions
and
21 deletions
opensipkd/base/views/user_login.py
opensipkd/base/views/user_login.py
View file @
caf8c1c
...
@@ -19,10 +19,13 @@ Perubahan Mendasar dari fungsi login adalah:
...
@@ -19,10 +19,13 @@ Perubahan Mendasar dari fungsi login adalah:
result object dari fungsi tersebut harus berupa class User()
result object dari fungsi tersebut harus berupa class User()
"""
"""
from
operator
import
ge
from
google.oauth2
import
id_token
from
google.auth.transport
import
requests
import
os
import
os
from
random
import
Random
from
random
import
Random
import
re
import
re
from
datetime
import
timedelta
,
datetime
from
datetime
import
date
,
timedelta
,
datetime
from
importlib
import
import_module
from
importlib
import
import_module
from
pyramid.request
import
Response
from
pyramid.request
import
Response
...
@@ -42,7 +45,7 @@ from . import one_hour, two_minutes
...
@@ -42,7 +45,7 @@ from . import one_hour, two_minutes
from
..models.users
import
User
,
ExternalIdentity
from
..models.users
import
User
,
ExternalIdentity
from
..models
import
Partner
from
..models
import
Partner
# , Partner
# , Partner
from
opensipkd.tools
import
create_now
,
set_user_log
,
get_settings
from
opensipkd.tools
import
create_now
,
set_user_log
,
get_settings
,
dmyhms
from
opensipkd.tools.buttons
import
btn_cancel
from
opensipkd.tools.buttons
import
btn_cancel
# from .. import get_urls
# from .. import get_urls
from
.base_views
import
CSRFSchema
,
BaseView
from
.base_views
import
CSRFSchema
,
BaseView
...
@@ -77,7 +80,18 @@ class Login(CSRFSchema):
...
@@ -77,7 +80,18 @@ class Login(CSRFSchema):
# http://deformdemo.repoze.org/interfield/
# http://deformdemo.repoze.org/interfield/
def
login_validator
(
form
,
value
):
def
login_validator
(
form
,
value
):
pass
exc
=
colander
.
Invalid
(
form
,
'Terlalu banyak percobaan'
)
request
=
form
.
request
if
request
.
session
.
get
(
"login_failed"
,
0
)
>
3
:
# message = "Login Gagal, terlalu banyak percobaan"
login_blocked
=
request
.
session
.
ses
[
"login_blocked"
]
if
login_blocked
and
login_blocked
>
datetime
.
now
():
exc
=
colander
.
Invalid
(
form
,
'Login Gagal, terlalu banyak percobaan, silahkan coba lagi setelah {}'
.
format
(
dmyhms
(
login_blocked
))
)
raise
exc
def
get_login_headers
(
request
,
user
):
def
get_login_headers
(
request
,
user
):
...
@@ -98,14 +112,25 @@ class LoginUser(object):
...
@@ -98,14 +112,25 @@ class LoginUser(object):
# self.identity=identity
# self.identity=identity
self
.
message
=
"Sukses Login"
self
.
message
=
"Sukses Login"
self
.
user
=
None
self
.
user
=
None
self
.
ses
=
request
.
session
self
.
ses
[
"login_failed"
]
=
self
.
ses
.
get
(
"login_failed"
,
0
)
self
.
ses
[
"login_blocked"
]
=
self
.
ses
.
get
(
"login_blocked"
)
def
login
(
self
,
values
,
user
=
None
):
def
login
(
self
,
values
,
user
=
None
):
settings
=
get_settings
()
self
.
user
=
user
and
user
or
User
.
get_by_identity
(
values
[
"username"
])
self
.
user
=
user
and
user
or
User
.
get_by_identity
(
values
[
"username"
])
if
not
self
.
user
or
not
UserService
.
check_password
(
if
not
self
.
user
or
not
UserService
.
check_password
(
self
.
user
,
values
[
"password"
]):
self
.
user
,
values
[
"password"
]):
self
.
message
=
"Login Gagal"
self
.
message
=
"Login Gagal"
set_user_log
(
self
.
message
,
self
.
request
,
log
,
values
[
"username"
])
set_user_log
(
self
.
message
,
self
.
request
,
log
,
values
[
"username"
])
self
.
ses
[
"login_failed"
]
+=
self
.
ses
.
get
(
"login_failed"
,
0
)
+
1
if
self
.
ses
[
"login_failed"
]
>
3
:
self
.
ses
[
"login_blocked"
]
=
datetime
.
now
()
+
\
timedelta
(
minutes
=
settings
.
get
(
"login_blocked_minutes"
,
1
))
return
return
self
.
ses
[
"login_failed"
]
=
0
self
.
ses
[
"login_blocked"
]
=
None
for
g
in
self
.
user
.
groups
:
for
g
in
self
.
user
.
groups
:
log
.
debug
(
f
"Group: {g.id} as {g.group_name}"
)
log
.
debug
(
f
"Group: {g.id} as {g.group_name}"
)
...
@@ -123,13 +148,11 @@ class Oauth2ParseExc(Exception):
...
@@ -123,13 +148,11 @@ class Oauth2ParseExc(Exception):
class
Oauth2UserExc
(
Exception
):
class
Oauth2UserExc
(
Exception
):
"""Error User Found"""
"""Error User Found"""
from
google.oauth2
import
id_token
from
google.auth.transport
import
requests
def
verify_android_token
(
token
,
web_client_id
):
def
verify_android_token
(
token
,
web_client_id
):
"""
"""
Verifies a Google ID token from an Android client on the backend.
Verifies a Google ID token from an Android client on the backend.
Args:
Args:
token (str): The ID token string received from the Android app.
token (str): The ID token string received from the Android app.
web_client_id (str): The Client ID for your *web application* in
web_client_id (str): The Client ID for your *web application* in
...
@@ -144,11 +167,11 @@ def verify_android_token(token, web_client_id):
...
@@ -144,11 +167,11 @@ def verify_android_token(token, web_client_id):
request
=
requests
.
Request
()
request
=
requests
.
Request
()
# Verify the token against Google's public keys
# Verify the token against Google's public keys
# The function automatically checks the token's signature, expiry,
# The function automatically checks the token's signature, expiry,
# and if it was issued by accounts.google.com.
# and if it was issued by accounts.google.com.
id_info
=
id_token
.
verify_oauth2_token
(
id_info
=
id_token
.
verify_oauth2_token
(
token
,
token
,
request
,
request
,
web_client_id
web_client_id
)
)
...
@@ -159,7 +182,7 @@ def verify_android_token(token, web_client_id):
...
@@ -159,7 +182,7 @@ def verify_android_token(token, web_client_id):
# Extract user information
# Extract user information
user_id
=
id_info
[
'sub'
]
user_id
=
id_info
[
'sub'
]
email
=
id_info
.
get
(
'email'
)
email
=
id_info
.
get
(
'email'
)
print
(
f
"Token verified for User ID: {user_id}, Email: {email}"
)
print
(
f
"Token verified for User ID: {user_id}, Email: {email}"
)
return
id_info
return
id_info
...
@@ -167,12 +190,13 @@ def verify_android_token(token, web_client_id):
...
@@ -167,12 +190,13 @@ def verify_android_token(token, web_client_id):
# Invalid token (e.g., signature mismatch, expired, wrong audience)
# Invalid token (e.g., signature mismatch, expired, wrong audience)
print
(
f
"Invalid token: {e}"
)
print
(
f
"Invalid token: {e}"
)
raise
raise
def
oauth2_login
(
request
,
params
=
None
):
def
oauth2_login
(
request
,
params
=
None
):
provider_name
=
params
and
params
[
"provider_name"
]
or
request
.
params
[
"provider_name"
]
provider_name
=
params
and
params
[
"provider_name"
]
or
request
.
params
[
"provider_name"
]
if
provider_name
==
"google"
:
if
provider_name
==
"google"
:
client_platform
=
params
and
params
.
get
(
"platform"
)
or
request
.
params
.
get
(
"platform"
)
or
"web"
client_platform
=
params
and
params
.
get
(
"platform"
)
or
request
.
params
.
get
(
"platform"
)
or
"web"
web_client_id
=
request
.
google_signin_client_id
web_client_id
=
request
.
google_signin_client_id
if
client_platform
==
"android"
:
if
client_platform
==
"android"
:
id_info
=
verify_android_token
(
id_info
=
verify_android_token
(
...
@@ -188,7 +212,7 @@ def oauth2_login(request, params=None):
...
@@ -188,7 +212,7 @@ def oauth2_login(request, params=None):
raise
Oauth2ParseExc
(
str
(
e
))
raise
Oauth2ParseExc
(
str
(
e
))
request
.
session
[
"id_info"
]
=
id_info
request
.
session
[
"id_info"
]
=
id_info
else
:
else
:
id_info
=
None
id_info
=
None
...
@@ -252,7 +276,7 @@ class ViewAuth(BaseView):
...
@@ -252,7 +276,7 @@ class ViewAuth(BaseView):
user
=
request
.
user
user
=
request
.
user
headers
=
get_login_headers
(
request
,
user
)
headers
=
get_login_headers
(
request
,
user
)
return
xhr_response
(
user
,
headers
)
return
xhr_response
(
user
,
headers
)
# return Response(json={"error":
# return Response(json={"error":
# {"code": "0000",
# {"code": "0000",
# "msg": message},
# "msg": message},
# "data":[]})
# "data":[]})
...
@@ -267,7 +291,7 @@ class ViewAuth(BaseView):
...
@@ -267,7 +291,7 @@ class ViewAuth(BaseView):
buttons
+=
(
Button
(
'register'
,
_
(
'Register'
)),)
buttons
+=
(
Button
(
'register'
,
_
(
'Register'
)),)
buttons
+=
(
Button
(
'reset'
,
_
(
'Reset'
)),
btn_cancel
,)
buttons
+=
(
Button
(
'reset'
,
_
(
'Reset'
)),
btn_cancel
,)
form
=
Form
(
schema
,
buttons
=
buttons
)
form
=
Form
(
schema
,
buttons
=
buttons
,
validator
=
login_validator
)
message
=
""
message
=
""
if
'cancel'
in
request
.
POST
:
if
'cancel'
in
request
.
POST
:
return
HTTPFound
(
location
=
request
.
home
)
return
HTTPFound
(
location
=
request
.
home
)
...
@@ -315,7 +339,7 @@ class ViewAuth(BaseView):
...
@@ -315,7 +339,7 @@ class ViewAuth(BaseView):
if
self
.
req
.
is_xhr
:
if
self
.
req
.
is_xhr
:
return
Response
(
json
=
{
"error"
:
{
"code"
:
-
1
,
return
Response
(
json
=
{
"error"
:
{
"code"
:
-
1
,
"msg"
:
login
.
message
},
"msg"
:
login
.
message
},
"data"
:[]})
"data"
:
[]})
next_url
=
f
"{request.route_url('base-login')}?next={next_url}"
next_url
=
f
"{request.route_url('base-login')}?next={next_url}"
return
HTTPFound
(
location
=
next_url
)
return
HTTPFound
(
location
=
next_url
)
...
@@ -361,7 +385,7 @@ class ViewAuth(BaseView):
...
@@ -361,7 +385,7 @@ class ViewAuth(BaseView):
# url=get_urls(request.route_url('login')),
# url=get_urls(request.route_url('login')),
# next_url=next_url,
# next_url=next_url,
# login=login, )
# login=login, )
if
self
.
req
.
is_xhr
:
if
self
.
req
.
is_xhr
:
form
.
set_appstruct
({})
form
.
set_appstruct
({})
struct
=
form
.
cstruct
struct
=
form
.
cstruct
...
@@ -370,7 +394,7 @@ class ViewAuth(BaseView):
...
@@ -370,7 +394,7 @@ class ViewAuth(BaseView):
if
not
csrf_token
:
if
not
csrf_token
:
csrf_token
=
new_csrf_token
(
request
)
csrf_token
=
new_csrf_token
(
request
)
struct
[
"csrf_token"
]
=
csrf_token
struct
[
"csrf_token"
]
=
csrf_token
log
.
debug
(
"CSRF Token:
%
s"
,
csrf_token
)
log
.
debug
(
"CSRF Token:
%
s"
,
csrf_token
)
log
.
info
(
"Form Struct:
%
s"
,
struct
)
log
.
info
(
"Form Struct:
%
s"
,
struct
)
return
self
.
resp_xhr
({
"data"
:
struct
})
return
self
.
resp_xhr
({
"data"
:
struct
})
...
@@ -420,12 +444,13 @@ class ViewAuth(BaseView):
...
@@ -420,12 +444,13 @@ class ViewAuth(BaseView):
return
dict
(
form
=
form
.
render
())
return
dict
(
form
=
form
.
render
())
def
xhr_response
(
user
,
headers
):
def
xhr_response
(
user
,
headers
):
partner
=
Partner
.
query_email
(
user
.
email
)
.
first
()
partner
=
Partner
.
query_email
(
user
.
email
)
.
first
()
mobile
=
partner
and
partner
.
mobile
or
""
mobile
=
partner
and
partner
.
mobile
or
""
nama
=
partner
and
partner
.
nama
or
""
nama
=
partner
and
partner
.
nama
or
""
data
=
{
data
=
{
"data"
:
"data"
:
[{
[{
"user_id"
:
user
.
user_name
,
"user_id"
:
user
.
user_name
,
"permission"
:
user
.
get_permissions
(),
"permission"
:
user
.
get_permissions
(),
...
@@ -434,11 +459,12 @@ def xhr_response(user, headers):
...
@@ -434,11 +459,12 @@ def xhr_response(user, headers):
"email"
:
user
.
email
,
"email"
:
user
.
email
,
"nama"
:
nama
,
"nama"
:
nama
,
}]
}]
}
}
return
Response
(
json
=
data
,
headerlist
=
headers
)
return
Response
(
json
=
data
,
headerlist
=
headers
)
def
redirect_login
(
request
,
user
):
def
redirect_login
(
request
,
user
):
set_user_log
(
"Login Sukses"
,
request
,
log
,
user
.
user_name
)
set_user_log
(
"Login Sukses"
,
request
,
log
,
user
.
user_name
)
for
g
in
user
.
groups
:
for
g
in
user
.
groups
:
...
@@ -618,7 +644,7 @@ class ViewPassword(BaseView):
...
@@ -618,7 +644,7 @@ class ViewPassword(BaseView):
def
get_passcode
(
self
):
def
get_passcode
(
self
):
if
not
self
.
req
.
authenticated_userid
:
if
not
self
.
req
.
authenticated_userid
:
return
HTTPNotFound
(
"Anda harus login dahulu"
)
return
HTTPNotFound
(
"Anda harus login dahulu"
)
if
"mail.sender_name"
not
in
self
.
settings
or
'mail.username'
not
in
self
.
settings
:
if
"mail.sender_name"
not
in
self
.
settings
or
'mail.username'
not
in
self
.
settings
:
return
HTTPNotAcceptable
(
"Anda harus login dahulu"
)
return
HTTPNotAcceptable
(
"Anda harus login dahulu"
)
...
@@ -638,6 +664,7 @@ class ViewPassword(BaseView):
...
@@ -638,6 +664,7 @@ class ViewPassword(BaseView):
sending_mail
(
self
.
req
,
user
,
subject
,
body
)
sending_mail
(
self
.
req
,
user
,
subject
,
body
)
return
dict
(
data
=
{
"message"
:
"Passcode sudah dikirim ke email Anda"
})
return
dict
(
data
=
{
"message"
:
"Passcode sudah dikirim ke email Anda"
})
class
ChangePasswordRequest
(
colander
.
Schema
):
class
ChangePasswordRequest
(
colander
.
Schema
):
new_password
=
colander
.
SchemaNode
(
new_password
=
colander
.
SchemaNode
(
colander
.
String
(),
widget
=
widget
.
CheckedPasswordWidget
())
colander
.
String
(),
widget
=
widget
.
CheckedPasswordWidget
())
...
...
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment