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 96c307cf
authored
Apr 28, 2026
by
aa.gustiana@gmail.com
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
init-branch-user-device
1 parent
166bffbe
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
316 additions
and
36 deletions
CHANGES.txt
development.ini
etc/production.ini
opensipkd/base/__init__.py
opensipkd/base/alembic/env.py
opensipkd/base/alembic/versions/1be80caf7f0a_update_user_session.py
opensipkd/base/models/users.py
opensipkd/base/views/__init__.py
opensipkd/base/views/user_login.py
pyproject.toml
CHANGES.txt
View file @
96c307c
5.0.3 2026-04-28
- Penambahan field session_id pada table users digunakan untuk membatasi jumlah
login hanya bisa ada pada satu device
- Penambahan parameter single_device
- Penambahan module change password saat menggunakan external-uim
5.0.2
Menambahkan path static 'mobi' and configuration 'mobile_static_path'
5.0.1
- Bug Saat get captcha session tidak berfungsi dimana ditambahkan checking melalui
file jpg yang tersimpan dalam folder captcha files
...
...
development.ini
View file @
96c307c
...
...
@@ -9,13 +9,12 @@
use
=
egg:opensipkd_base
reload_templates
=
true
pyramid.debug_all
=
true
pyramid.debug_authorization
=
false
pyramid.debug_authorization
=
true
pyramid.debug_notfound
=
true
pyramid.debug_routematch
=
true
pyramid.debug_templates
=
true
default_locale_name
=
id
sqlalchemy.url
=
postgresql://aagusti:a@localhost:5432/asinan
session.url
=
postgresql://aagusti:a@localhost:5432/asinan
sqlalchemy.url
=
postgresql://aagusti:a@localhost:5432/demos
pyramid.includes
=
pyramid_tm
pyramid_beaker
...
...
@@ -27,12 +26,21 @@ pyramid.includes =
; opensipkd.pbb.monitoring
session.url
=
postgresql://aagusti:a@localhost:5432/demo
session.type
=
ext:database
session.secret
=
s0s3cr3ts
session.cookie_expires
=
true
session.key
=
WhatEver
session.timeout
=
3000
session.lock_dir
=
%(here)s/tmp
session.httponly
=
True
; Prevents JavaScript from stealing the session cookie (mitigates XSS).
; session.secure=True
; Forces the cookie to be sent only over HTTPS.
session.samesite
=
'Lax'
; or 'Strict' Prevents Cross-Site Request Forgery (CSRF) by limiting cookie scope.
; session.timeout 1800 (30 mins) Automatically expires the session after inactivity.
timezone = Asia/Jakarta
;localization = id_ID.UTF-8
#localization = Indonesian_indonesia.1252
...
...
@@ -43,6 +51,8 @@ temp_files = C:\tmp
partner_doc
=
C:
\\
tmp
\\
docs
\\
partner
\\
;home_tpl = opensipkd.samsat.jabar.views:templates/login.pt
;login_tpl = opensipkd.samsat.jabar.views:templates/login.pt
# Securitty
single_device = true
login_captcha
=
0
# Registrasi User
...
...
@@ -141,8 +151,8 @@ keys = console, filelog
keys
=
generic
[logger_root]
level
=
WARN
handlers
=
level
=
DEBUG
handlers
=
console, filelog
#, tabel
[logger_opensipkd]
...
...
@@ -151,7 +161,7 @@ handlers =console, filelog
qualname
=
opensipkd
[logger_sqlalchemy]
level
=
WARN
level
=
DEBUG
handlers
=
qualname = sqlalchemy.engine
# "level = INFO" logs SQL queries.
...
...
@@ -183,13 +193,13 @@ format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
[alembic_ziggurat]
script_location
=
ziggurat_foundations:migrations
sqlalchemy.url
=
postgresql://aagusti:a@localhost:5432/asinan
;
[alembic_ziggurat]
;
script_location = ziggurat_foundations:migrations
;
sqlalchemy.url = postgresql://aagusti:a@localhost:5432/asinan
[alembic_base]
script_location
=
opensipkd.base:alembic
sqlalchemy.url
=
postgresql://aagusti:a@localhost:5432/
asinan
sqlalchemy.url
=
postgresql://aagusti:a@localhost:5432/
demos
[alembic_base_init]
script_location
=
opensipkd.base:alembic
...
...
etc/production.ini
0 → 100644
View file @
96c307c
###
# app configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
;Digunakan untuk setting sebagai app:main atau app:virdir (virtual directory)
[app:main]
;[app:virdir]
use
=
egg:opensipkd_base
reload_templates
=
true
pyramid.debug_all
=
true
pyramid.debug_authorization
=
false
pyramid.debug_notfound
=
true
pyramid.debug_routematch
=
true
pyramid.debug_templates
=
true
default_locale_name
=
id
sqlalchemy.url
=
postgresql://user:pass@server:port/db
sqlalchemy.pool_pre_ping
=
True
sqlalchemy.pool_size
=
20
sqlalchemy.echo
=
False
session.url
=
postgresql://user:pass@server:port/db
pyramid.includes
=
pyramid_tm
pyramid_beaker
pyramid_chameleon
pyramid_rpc.jsonrpc
# pyramid_debugtoolbar
session.type
=
ext:database
session.secret
=
s0s3cr3ts
session.cookie_expires
=
true
session.key
=
WhatEver
session.timeout
=
3000
session.lock_dir
=
%(here)s/tmp
timezone
=
Asia/Jakarta
;localization = id_ID.UTF-8
#localization = Indonesian_indonesia.1252
localization
=
English_Australia.1252
# Base Configuration
# temp_files digunakan untuk menyimpan file sementara, seperti hasil export,
# hasil upload, dll default C:\tmp atau /tmp
temp_files
=
%(here)s/../tmp
# partner_doc digunakan untuk menyimpan file upload dokumen partner harus permanen,
# default /tmp/docs/partner/ C:\\tmp\\docs\\partner\\
partner_doc
=
%(here)s/../tmp/docs/partner/
# Registrasi User
# Digunakan untuk mengatur apakah aplikasi akan menyediakan fitur registrasi user baru,
allow_register
=
1
# Template form registrasi, jika kosong akan menggunakan template default dari opensipkd,
reg_form
=
# Digunakan untuk mengatur apakah aplikasi akan meminta input nomor KTP, jika 1
# maka akan muncul field input nomor KTP pada form registrasi,
reg_idcard = 1
# Digunakan untuk mengatur apakah aplikasi akan meminta input nomor Captcha, jika 1
# maka akan muncul field input nomor Captcha pada form registrasi,
reg_captcha
=
1
# reg_verify digunakan untuk mengatur apakah aplikasi akan dilakukan verifikasi
# manual email verifikasi setelah registrasi, jika 1 maka aplikasi akan mengirim
# email verifikasi ke email yang didaftarkan oleh user,
reg_verify
=
1
# Custome template untuk login ke aplikasi
login_tpl
=
# Menampilkan captcha pada form login, jika 1 maka akan muncul field input nomor Captcha pada form login,
login_captcha
=
1
static_files
=
%(here)s/../files
captcha_files
=
%(here)s/../tmp/captcha
;company = Opensipkd
;ibukota = Bekasi
;departement = IT
;address_1 = Jalan....
;address_2 = Bekasi ...
;
;center.phone = 021123456789
;center.mobile = 081311045668
;center.email = aa.gustiana@gmail.com
;center.email_password =
;center.smtp_server =
;
;#_host = http://localhost:5433/demo2
;
;unoconv_py = C:\Program Files\LibreOffice\program\python.exe
;unoconv_bin = C:\product\venv-lates\Scripts\unoconv
;
# `menus` Digunakan untuk mengatur menu apa saja yang akan ditampilkan pada aplikasi,
# format menu: url:label atu routekode:label antar menu dipisahkan dengan baris baru, contoh:
; menus =
; bphtb:BPHTB
; five:Five
# Default nama aplikasi
;app_name = OPENSIPKD-APP
# digunakan untuk mengatur apakah user bisa berganti2 dinas
;change_unit = False
;departemen_chg_id = 3
; PROXY Setting tambahan untuk proxy
;trusted_proxy_headers = "forwarded x-forwarded-for x-forwarded-host x-forwarded-proto x-forwarded-port"
;url_prefix='/wsgi'
;[composite:main]
;use = egg:rutter#urlmap
;/ = opensipkd_base
;/wsgi/ = opensipkd_base
;[filter:proxy-prefix]
;use = egg:PasteDeploy#prefix
# digunakan untuk mengatur prefix url jika aplikasi dijalankan di virtual
# directory, contoh:
# jika aplikasi dijalankan di http://localhost:6543/virdir maka prefix yang digunakan adalah /virdir,
# jika aplikasi dijalankan di http://localhost:6543/ maka prefix yang digunakan adalah /,
# jika aplikasi dijalankan di http://localhost:6543/app/ maka prefix yang digunakan adalah /app, dst
;prefix = /virdir
;[pipeline:main]
;pipeline =
; proxy-prefix
; opensipkd_base
[server:main]
use
=
egg:waitress#main
host
=
0.0.0.0
port
=
6543
[alembic_ziggurat]
script_location
=
ziggurat_foundations:migrations
sqlalchemy.url
=
postgresql://aagusti:a@localhost:5432/demo2
[alembic_base]
script_location
=
opensipkd.base.scripts:alembic
sqlalchemy.url
=
postgresql://aagusti:a@localhost:5432/demo2
[pytest]
filterwarnings
=
error
ignore::UserWarning
ignore:function
ham\(\)
is
deprecated:DeprecationWarning
# Begin logging configuration
[loggers]
keys
=
root, opensipkd, sqlalchemy
[handlers]
keys
=
console, filelog
#, tabel
[formatters]
keys
=
generic
[logger_root]
level
=
WARN
handlers
=
#, tabel
[logger_opensipkd]
level
=
DEBUG
handlers
=
console, filelog
qualname
=
opensipkd
[logger_sqlalchemy]
level
=
WARN
handlers
=
qualname
=
sqlalchemy.engine
# "level = INFO" logs SQL queries.
# "level = DEBUG" logs SQL queries and results.
# "level = WARN" logs neither. (Recommended for production systems.)
[handler_filelog]
class
=
FileHandler
; args = ('log_file','a')
args
=
('/tmp/logs/opensipkd.log','a')
level
=
DEBUG
formatter
=
generic
[handler_console]
class
=
StreamHandler
args
=
(sys.stderr,)
level
=
NOTSET
formatter
=
generic
[handler_tabel]
class
=
opensipkd.base.handlers.SQLAlchemyHandler
args
=
()
level
=
WARN
formatter
=
generic
[formatter_generic]
format
=
%(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
opensipkd/base/__init__.py
View file @
96c307c
...
...
@@ -336,6 +336,8 @@ def main(global_config, **settings):
BASE_CLASS
.
route_from_csv
(
config
,
filename
=
routes_file
)
BASE_CLASS
.
route_from_list
(
config
)
BASE_CLASS
.
static_view
(
config
,
settings
=
settings
)
BASE_CLASS
.
single_device
=
settings
.
get
(
"single_device"
,
"false"
)
.
lower
()
==
"true"
config
.
scan
(
"."
)
# _logging.debug(config)
return
config
.
make_wsgi_app
()
...
...
@@ -457,6 +459,16 @@ def add_cors_headers_response_callback(event):
event
.
request
.
add_response_callback
(
cors_headers
)
@subscriber
(
NewRequest
)
def
check_single_device_session
(
event
):
request
=
event
.
request
if
request
.
user
and
BASE_CLASS
.
single_device
:
if
request
.
user
.
session_id
!=
request
.
session
.
id
:
request
.
session
.
invalidate
()
request
.
session
.
flash
(
"Sesi Anda telah berakhir karena login dari perangkat lain."
,
"error"
)
raise
HTTPFound
(
location
=
request
.
route_url
(
'base-login'
),
headers
=
forget
(
request
))
@subscriber
(
BeforeRender
)
def
add_global_render
(
event
):
event
[
'has_permission'
]
=
has_permission_
...
...
@@ -504,6 +516,7 @@ class BaseApp():
self
.
login_captcha
=
0
self
.
base_dir
=
os
.
path
.
split
(
__file__
)[
0
]
self
.
reg_nip
=
0
self
.
single_device
=
"false"
def
get_route_file
(
self
,
filename
=
"routes.csv"
):
fullpath
=
os
.
path
.
join
(
self
.
base_dir
,
'scripts'
,
'data'
,
filename
)
...
...
opensipkd/base/alembic/env.py
View file @
96c307c
...
...
@@ -26,6 +26,11 @@ target_metadata = Base.metadata
# ... etc.
def
include_object
(
object
,
name
,
type_
,
reflected
,
compare_to
):
if
type_
==
"table"
and
name
!=
"users"
:
return
False
return
True
def
run_migrations_offline
()
->
None
:
"""Run migrations in 'offline' mode.
...
...
@@ -44,8 +49,8 @@ def run_migrations_offline() -> None:
target_metadata
=
target_metadata
,
literal_binds
=
True
,
dialect_opts
=
{
"paramstyle"
:
"named"
},
version_table
=
'alembic_base'
version_table
=
'alembic_base'
,
include_object
=
include_object
)
with
context
.
begin_transaction
():
...
...
opensipkd/base/alembic/versions/1be80caf7f0a_update_user_session.py
0 → 100644
View file @
96c307c
"""update_user_session
Revision ID: 1be80caf7f0a
Revises: 8de8d8168688
Create Date: 2026-04-28 01:46:36.931499
"""
# revision identifiers, used by Alembic.
revision
=
'1be80caf7f0a'
down_revision
=
'8de8d8168688'
branch_labels
=
None
depends_on
=
None
from
alembic
import
op
import
sqlalchemy
as
sa
def
upgrade
():
# ### commands auto generated by Alembic - please adjust! ###
op
.
add_column
(
'users'
,
sa
.
Column
(
'session_id'
,
sa
.
String
(
length
=
256
),
nullable
=
True
))
op
.
add_column
(
'users'
,
sa
.
Column
(
'multi_device'
,
sa
.
SmallInteger
(),
nullable
=
True
))
# ### end Alembic commands ###
def
downgrade
():
# ### commands auto generated by Alembic - please adjust! ###
op
.
drop_column
(
'users'
,
'multi_device'
)
op
.
drop_column
(
'users'
,
'session_id'
)
# ### end Alembic commands ###
opensipkd/base/models/users.py
View file @
96c307c
from
datetime
import
datetime
import
logging
from
math
import
log
from
requests
import
session
import
pytz
import
sqlalchemy
as
sa
from
pyramid.authorization
import
(
Allow
,
Authenticated
,
ALL_PERMISSIONS
)
from
sqlalchemy
import
(
Column
,
Integer
,
DateTime
,
String
)
Column
,
Integer
,
DateTime
,
S
mallInteger
,
S
tring
)
from
sqlalchemy.orm
import
(
relationship
,
backref
)
from
ziggurat_foundations
import
ziggurat_model_init
from
ziggurat_foundations.models.base
import
BaseModel
...
...
@@ -107,7 +109,8 @@ class _User(UserMixin, BaseModel):
api_key
=
Column
(
String
(
256
))
partner_id
=
Column
(
Integer
)
# , ForeignKey(Partner.id))
company_id
=
Column
(
Integer
)
# , ForeignKey(Partner.id))
session_id
=
Column
(
String
(
256
))
multi_device
=
Column
(
SmallInteger
)
# partners = relationship(Partner, backref=backref('users'))
def
_get_password
(
self
):
...
...
opensipkd/base/views/__init__.py
View file @
96c307c
...
...
@@ -6,7 +6,7 @@ from pyramid.httpexceptions import (
HTTPSeeOther
)
from
pyramid.interfaces
import
IRoutesMapper
from
pyramid.view
import
view_config
from
opensipkd.base
import
get_params
,
get_home
from
opensipkd.base
import
get_params
from
pyramid.renderers
import
render_to_response
from
.base_views
import
BaseView
from
datetime
import
timedelta
...
...
opensipkd/base/views/user_login.py
View file @
96c307c
...
...
@@ -86,13 +86,23 @@ def login_validator(form, value):
def
get_login_headers
(
request
,
user
):
if
not
request
.
is_xhr
and
BASE_CLASS
.
single_device
and
\
not
user
.
multi_device
:
# if user.session_id and user.session_id != request.session.id:
# from beaker.session import Session
# Session(request.headers, id=user.session_id).delete()
user
.
session_id
=
request
.
session
.
id
User
.
db_session
.
add
(
user
)
User
.
db_session
.
flush
()
UserService
.
regenerate_security_code
(
user
)
headers
=
remember
(
request
,
user
.
id
)
headers
.
append
((
"Token"
,
user
.
security_code
))
log
.
debug
(
headers
)
user
.
last_login_date
=
create_now
()
DBS
ession
.
add
(
user
)
DBS
ession
.
flush
()
User
.
db_s
ession
.
add
(
user
)
User
.
db_s
ession
.
flush
()
return
headers
...
...
@@ -237,9 +247,9 @@ def oauth2_login(request, params=None):
user
=
User
()
user
.
from_dict
(
values
)
DBS
ession
.
add
(
user
)
DBS
ession
.
flush
()
DBS
ession
.
refresh
(
user
)
User
.
db_s
ession
.
add
(
user
)
User
.
db_s
ession
.
flush
()
User
.
db_s
ession
.
refresh
(
user
)
values
=
{
'external_id'
:
id_info
[
'sub'
],
'external_user_name'
:
id_info
[
"name"
],
'external_email'
:
id_info
[
"email"
],
...
...
@@ -248,8 +258,8 @@ def oauth2_login(request, params=None):
"status"
:
1
}
external
=
ExternalIdentity
()
external
.
from_dict
(
values
)
DBS
ession
.
add
(
external
)
DBS
ession
.
flush
()
User
.
db_s
ession
.
add
(
external
)
User
.
db_s
ession
.
flush
()
if
user
and
user
.
status
!=
1
:
raise
Oauth2UserExc
(
"User anda masih menunggu verifikasi atau lagi di blokir"
)
...
...
@@ -320,7 +330,7 @@ class ViewAuth(BaseView):
# start cek external module
pckgs
=
get_params
(
'external-uim'
)
if
user
:
external_user
=
DBS
ession
.
query
(
ExternalIdentity
)
.
\
external_user
=
self
.
db_s
ession
.
query
(
ExternalIdentity
)
.
\
filter_by
(
local_user_id
=
user
.
id
,
external_user_name
=
identity
)
.
first
()
pckgs
=
external_user
and
pckgs
or
None
...
...
@@ -334,9 +344,7 @@ class ViewAuth(BaseView):
log
.
warn
(
str
(
e
))
request
.
session
.
flash
(
str
(
e
),
"error"
)
return
HTTPFound
(
location
=
request
.
route_url
(
'base-login'
))
else
:
login
=
LoginUser
(
self
.
req
)
if
not
login
.
login
(
values
,
user
):
request
.
session
.
flash
(
login
.
message
,
"error"
)
...
...
@@ -475,8 +483,8 @@ def xhr_response(user, headers):
def
redirect_login
(
request
,
user
):
set_user_log
(
"Login Sukses"
,
request
,
log
,
user
.
user_name
)
for
g
in
user
.
groups
:
log
.
debug
(
f
"Group: {g.id} as {g.group_name}"
)
#
for g in user.groups:
#
log.debug(f"Group: {g.id} as {g.group_name}")
headers
=
get_login_headers
(
request
,
user
)
log
.
debug
(
request
.
headers
)
...
...
@@ -524,7 +532,7 @@ class ViewPassword(BaseView):
if
'submit'
in
request
.
POST
:
controls
=
request
.
POST
.
items
()
identity
=
request
.
POST
.
get
(
'email'
)
q
=
DBS
ession
.
query
(
User
)
.
filter_by
(
email
=
identity
)
q
=
self
.
db_s
ession
.
query
(
User
)
.
filter_by
(
email
=
identity
)
schema
.
user
=
user
=
q
.
first
()
try
:
c
=
form
.
validate
(
controls
)
...
...
@@ -599,7 +607,7 @@ class ViewPassword(BaseView):
request
.
session
.
flash
(
'Anda sudah login'
,
'error'
)
return
HTTPFound
(
location
=
f
"{request.home}"
)
code
=
request
.
matchdict
[
'code'
]
q
=
DBS
ession
.
query
(
User
)
.
filter_by
(
security_code
=
code
)
q
=
self
.
db_s
ession
.
query
(
User
)
.
filter_by
(
security_code
=
code
)
user
=
q
.
first
()
now
=
create_now
()
if
not
user
or
now
-
user
.
security_code_date
>
one_hour
:
...
...
@@ -624,7 +632,7 @@ class ViewPassword(BaseView):
user
.
security_code
=
None
UserService
.
set_password
(
user
,
c
[
'new_password'
])
DBS
ession
.
add
(
user
)
self
.
db_s
ession
.
add
(
user
)
headers
=
get_login_headers
(
request
,
user
)
request
.
session
.
flash
(
'Password baru Anda sudah disimpan.'
)
set_user_log
(
"Change Password"
,
request
,
log
)
...
...
@@ -645,7 +653,7 @@ class ViewPassword(BaseView):
if
'recreate'
not
in
request
.
POST
:
return
HTTPFound
(
location
=
f
"{request.home}"
)
request
.
user
.
api_key
=
api_key
=
generate_api_key
()
DBS
ession
.
add
(
request
.
user
)
self
.
db_s
ession
.
add
(
request
.
user
)
msg
=
'API Key Anda yang baru {}'
.
format
(
api_key
)
request
.
session
.
flash
(
msg
)
return
HTTPFound
(
location
=
f
"{request.home}"
)
...
...
@@ -659,8 +667,8 @@ class ViewPassword(BaseView):
user
=
self
.
req
.
user
user
.
security_code
=
Random
()
.
randint
(
10000
,
99999
)
DBS
ession
.
add
(
user
)
DBS
ession
.
flush
()
self
.
db_s
ession
.
add
(
user
)
self
.
db_s
ession
.
flush
()
minutes
=
two_minutes
data
=
dict
(
passcode
=
user
.
security_code
,
minutes
=
minutes
)
here
=
os
.
path
.
abspath
(
os
.
path
.
dirname
(
__file__
))
...
...
@@ -788,7 +796,7 @@ def send_email_pending(
here
=
os
.
path
.
abspath
(
os
.
path
.
dirname
(
__file__
))
body_file
=
os
.
path
.
join
(
here
,
body_default_file
)
with
open
(
body_file
)
as
f
:
with
open
(
body_file
,
encoding
=
'utf-8'
)
as
f
:
body_tpl
=
f
.
read
()
body
=
_
(
body_msg_id
,
default
=
body_tpl
)
sending_mail
(
request
,
user
,
subject
,
body
)
...
...
@@ -804,5 +812,5 @@ def regenerate_security_code(user, hour=1.0):
UserService
.
regenerate_security_code
(
user
)
user
.
security_code_date
=
create_now
()
log
.
debug
(
"Security code:
%
s"
,
user
.
security_code
)
DBS
ession
.
add
(
user
)
User
.
db_s
ession
.
add
(
user
)
return
hour
pyproject.toml
View file @
96c307c
...
...
@@ -15,7 +15,7 @@ show_error_codes = true
[project]
name
=
'opensipkd_base'
version
=
'
5.0
'
version
=
'
5.0
.1
'
dependencies
=
[
'deform'
,
'colander'
,
...
...
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