diff --git a/.github/workflows/nightly_unit_tests.yml b/.github/workflows/nightly_unit_tests.yml
deleted file mode 100644
index 01dd2112..00000000
--- a/.github/workflows/nightly_unit_tests.yml
+++ /dev/null
@@ -1,73 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: Nigthly Unit Tests Run
-
-on:
- schedule:
- - cron: '0 0 * * *' # At the end of every day
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "unit-tests"
- unit-tests:
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
- env:
- APP_ENV: testing
- APP_DEBUG: true
- APP_KEY: base64:4vh0op/S1dAsXKQ2bbdCfWRyCI9r8NNIdPXyZWt9PX4=
- DEV_EMAIL_TO: smarcet@gmail.com
- APP_URL: http://localhost
- DB_CONNECTION: mysql
- DB_HOST: 127.0.0.1
- DB_PORT: 3306
- DB_DATABASE: idp_test
- DB_USERNAME: root
- DB_PASSWORD: 1qaz2wsx
- REDIS_HOST: 127.0.0.1
- REDIS_PORT: 6379
- REDIS_DB: 0
- REDIS_PASSWORD: 1qaz2wsx
- REDIS_DATABASES: 16
- SSL_ENABLED: false
- SESSION_DRIVER: redis
- PHP_VERSION: 8.3
- services:
- mysql:
- image: mysql:8.0
- env:
- MYSQL_ROOT_PASSWORD: ${{env.DB_PASSWORD}}
- MYSQL_DATABASE: ${{env.DB_DATABASE}}
- ports:
- - 3306:3306
- options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
- steps:
- - name: Create Redis
- uses: supercharge/redis-github-action@1.8.1
- with:
- redis-port: ${{env.REDIS_PORT}}
- redis-password: ${{env.REDIS_PASSWORD}}
- # Downloads a copy of the code in your repository before running CI tests
- - name: Check out repository code
- uses: actions/checkout@v4
- - name: Install PHP
- uses: "shivammathur/setup-php@v2"
- with:
- php-version: ${{env.PHP_VERSION}}
- extensions: pdo_mysql, mbstring, exif, pcntl, bcmath, sockets, gettext, apcu
- - name: Install dependencies
- uses: "ramsey/composer-install@v3"
- env:
- COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.PAT }}"} }'
- - name: 'Run Tests'
- run: |
- ./update_doctrine.sh
- php artisan doctrine:migrations:migrate --no-interaction
- php artisan db:seed --force
- vendor/bin/phpunit --log-junit results.xml
- - name: 'Upload Unit Test Output'
- uses: actions/upload-artifact@v4
- with:
- name: output
- path: /output.log
- retention-days: 5
diff --git a/.github/workflows/pull_request_unit_tests.yml b/.github/workflows/pull_request_unit_tests.yml
deleted file mode 100644
index 319b1d1d..00000000
--- a/.github/workflows/pull_request_unit_tests.yml
+++ /dev/null
@@ -1,76 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: Unit Tests On Pull Request
-
-# Controls when the workflow will run
-on:
- # Triggers the workflow on pull request events but only for the "main" branch
- pull_request:
- types: [opened, reopened, edited,synchronize]
- branches: [ "main" ]
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "unit-tests"
- unit-tests:
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
- env:
- APP_ENV: testing
- APP_DEBUG: true
- APP_KEY: base64:4vh0op/S1dAsXKQ2bbdCfWRyCI9r8NNIdPXyZWt9PX4=
- DEV_EMAIL_TO: smarcet@gmail.com
- APP_URL: http://localhost
- DB_CONNECTION: mysql
- DB_HOST: 127.0.0.1
- DB_PORT: 3306
- DB_DATABASE: idp_test
- DB_USERNAME: root
- DB_PASSWORD: 1qaz2wsx
- REDIS_HOST: 127.0.0.1
- REDIS_PORT: 6379
- REDIS_DB: 0
- REDIS_PASSWORD: 1qaz2wsx
- REDIS_DATABASES: 16
- SSL_ENABLED: false
- SESSION_DRIVER: redis
- PHP_VERSION: 8.3
- services:
- mysql:
- image: mysql:8.0
- env:
- MYSQL_ROOT_PASSWORD: ${{env.DB_PASSWORD}}
- MYSQL_DATABASE: ${{env.DB_DATABASE}}
- ports:
- - 3306:3306
- options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
- steps:
- - name: Create Redis
- uses: supercharge/redis-github-action@1.8.1
- with:
- redis-port: ${{env.REDIS_PORT}}
- redis-password: ${{env.REDIS_PASSWORD}}
- # Downloads a copy of the code in your repository before running CI tests
- - name: Check out repository code
- uses: actions/checkout@v4
- - name: Install PHP
- uses: "shivammathur/setup-php@v2"
- with:
- php-version: ${{env.PHP_VERSION}}
- extensions: pdo_mysql, mbstring, exif, pcntl, bcmath, sockets, gettext, apcu
- - name: Install dependencies
- uses: "ramsey/composer-install@v3"
- env:
- COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.PAT }}"} }'
- - name: 'Run Tests'
- run: |
- ./update_doctrine.sh
- php artisan doctrine:migrations:migrate --no-interaction
- php artisan db:seed --force
- vendor/bin/phpunit --log-junit results.xml
- - name: 'Upload Unit Test Output'
- uses: actions/upload-artifact@v4
- with:
- name: output
- path: /output.log
- retention-days: 5
\ No newline at end of file
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
deleted file mode 100644
index af88d4f7..00000000
--- a/.github/workflows/push.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: Unit Tests On Push
-
-# Controls when the workflow will run
-on: push
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "unit-tests"
- unit-tests:
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
- env:
- APP_ENV: testing
- APP_DEBUG: true
- APP_KEY: base64:4vh0op/S1dAsXKQ2bbdCfWRyCI9r8NNIdPXyZWt9PX4=
- DEV_EMAIL_TO: smarcet@gmail.com
- APP_URL: http://localhost
- DB_CONNECTION: mysql
- DB_HOST: 127.0.0.1
- DB_PORT: 3306
- DB_DATABASE: idp_test
- DB_USERNAME: root
- DB_PASSWORD: 1qaz2wsx
- REDIS_HOST: 127.0.0.1
- REDIS_PORT: 6379
- REDIS_DB: 0
- REDIS_PASSWORD: 1qaz2wsx
- REDIS_DATABASES: 16
- SSL_ENABLED: false
- SESSION_DRIVER: redis
- PHP_VERSION: 8.3
- OTEL_SDK_DISABLED: true
- OTEL_SERVICE_ENABLED: false
- services:
- mysql:
- image: mysql:8.0
- env:
- MYSQL_ROOT_PASSWORD: ${{env.DB_PASSWORD}}
- MYSQL_DATABASE: ${{env.DB_DATABASE}}
- ports:
- - 3306:3306
- options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
- steps:
- - name: Create Redis
- uses: supercharge/redis-github-action@1.8.1
- with:
- redis-port: ${{env.REDIS_PORT}}
- redis-password: ${{env.REDIS_PASSWORD}}
- # Downloads a copy of the code in your repository before running CI tests
- - name: Check out repository code
- uses: actions/checkout@v4
- - name: Install PHP
- uses: "shivammathur/setup-php@v2"
- with:
- php-version: ${{env.PHP_VERSION}}
- extensions: pdo_mysql, mbstring, exif, pcntl, bcmath, sockets, gettext, apcu
- - name: Install dependencies
- uses: "ramsey/composer-install@v3"
- env:
- COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.PAT }}"} }'
- - name: 'Run Tests'
- run: |
- ./update_doctrine.sh
- php artisan doctrine:migrations:migrate --no-interaction
- php artisan db:seed --force
- vendor/bin/phpunit --log-junit results.xml
- - name: 'Upload Unit Test Output'
- uses: actions/upload-artifact@v4
- with:
- name: output
- path: results.xml
- retention-days: 5
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 00000000..903f6392
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,20 @@
+
+
+ Options -MultiViews
+
+
+ RewriteEngine On
+
+ # Redirect Trailing Slashes If Not A Folder...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^(.*)/$ /$1 [L,R=301]
+
+ # Handle Front Controller...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [L]
+
+ # Handle Authorization Header
+ RewriteCond %{HTTP:Authorization} .
+ RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+
diff --git a/.nojekyll b/.nojekyll
new file mode 100644
index 00000000..e69de29b
diff --git a/assets/css/auth/consent.css b/assets/css/auth/consent.css
new file mode 100644
index 00000000..4d53558a
--- /dev/null
+++ b/assets/css/auth/consent.css
@@ -0,0 +1,12 @@
+.consents-container{
+ max-height: 250px;
+ overflow-y: auto;
+}
+
+.consents-footer{
+ padding-top: 5px;
+}
+
+.consents-footer button{
+ padding: 10px;
+}
\ No newline at end of file
diff --git a/assets/css/auth/email.css b/assets/css/auth/email.css
new file mode 100644
index 00000000..ef1c6786
--- /dev/null
+++ b/assets/css/auth/email.css
@@ -0,0 +1,90 @@
+body{
+ color: #63738a;
+ background: white;
+ font-family: 'Roboto', sans-serif;
+}
+.form-control{
+ height: 40px;
+ box-shadow: none;
+ color: #969fa4;
+}
+.form-control:focus{
+ border-color: #63738a;
+}
+.form-control, .btn{
+ border-radius: 3px;
+}
+.signup-form{
+ max-width: 400px;
+ min-width: 290px;
+ margin: 0 auto;
+ padding: 30px 0;
+}
+.signup-form h2{
+ color: #636363;
+ margin: 0 0 15px;
+ position: relative;
+ text-align: center;
+}
+
+.signup-form h2:before, .signup-form h2:after{
+ content: "";
+ height: 2px;
+ width: 10%;
+ background: #d4d4d4;
+ position: absolute;
+ top: 50%;
+ z-index: 2;
+}
+
+.signup-form h2:before{
+ left: 0;
+}
+.signup-form h2:after{
+ right: 0;
+}
+.signup-form .hint-text{
+ color: #999;
+ margin-bottom: 30px;
+ text-align: center;
+}
+.signup-form form{
+ color: #999;
+ border-radius: 3px;
+ margin-bottom: 15px;
+ background: #f2f3f7;
+ box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+ padding: 30px;
+}
+.signup-form .form-group{
+ margin-bottom: 20px;
+}
+.signup-form input[type="checkbox"]{
+ margin-top: 3px;
+}
+.signup-form .btn{
+ font-size: 16px;
+ font-weight: bold;
+ min-width: 140px;
+ outline: none !important;
+}
+.signup-form .row div:first-child{
+ padding-right: 10px;
+}
+.signup-form .row div:last-child{
+ padding-left: 10px;
+}
+.signup-form a{
+ color: #63738a;
+ text-decoration: underline;
+}
+.signup-form a:hover{
+ text-decoration: none;
+}
+.signup-form form a{
+ color: #5cb85c;
+ text-decoration: none;
+}
+.signup-form form a:hover{
+ text-decoration: underline;
+}
\ No newline at end of file
diff --git a/assets/css/auth/login.css b/assets/css/auth/login.css
new file mode 100644
index 00000000..ac7e6e7b
--- /dev/null
+++ b/assets/css/auth/login.css
@@ -0,0 +1,9 @@
+.field-icon {
+ float: right;
+ margin-left: -25px;
+ margin-top: -25px;
+ margin-right: 10px;
+ position: relative;
+ z-index: 2;
+ cursor: pointer;
+}
diff --git a/assets/css/auth/register.css b/assets/css/auth/register.css
new file mode 100644
index 00000000..5e654d60
--- /dev/null
+++ b/assets/css/auth/register.css
@@ -0,0 +1,99 @@
+body{
+ color: #63738a;
+ background: white;
+ font-family: 'Roboto', sans-serif;
+}
+
+.form-control{
+ height: 40px;
+ box-shadow: none;
+ color: #969fa4;
+}
+
+.form-control:focus{
+ border-color: #63738a;
+}
+
+.form-control, .btn{
+ border-radius: 3px;
+}
+
+.signup-form{
+
+}
+
+.signup-form h2{
+ color: #636363;
+ margin: 0 0 15px;
+ position: relative;
+ text-align: center;
+}
+
+.signup-form h2:before, .signup-form h2:after{
+ content: "";
+ height: 2px;
+ width: 30%;
+ background: #d4d4d4;
+ position: absolute;
+ top: 50%;
+ z-index: 2;
+}
+
+.signup-form h2:before{
+ left: 0;
+}
+.signup-form h2:after{
+ right: 0;
+}
+.signup-form .hint-text{
+ color: #999;
+ margin-bottom: 30px;
+ text-align: center;
+}
+
+.signup-form form{
+ color: #999;
+ border-radius: 3px;
+ margin-bottom: 15px;
+ background: #f2f3f7;
+ box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+ padding: 30px;
+}
+
+.signup-form .form-group{
+ margin-bottom: 20px;
+}
+.signup-form input[type="checkbox"]{
+ margin-top: 3px;
+}
+.signup-form .btn{
+ font-size: 16px;
+ font-weight: bold;
+ min-width: 140px;
+ outline: none !important;
+}
+.signup-form .row div:first-child{
+ padding-right: 10px;
+}
+.signup-form .row div:last-child{
+ padding-left: 10px;
+}
+.signup-form a{
+ color: #63738a;
+ text-decoration: underline;
+}
+.signup-form a:hover{
+ text-decoration: none;
+}
+.signup-form form a{
+ color: #5cb85c;
+ text-decoration: none;
+}
+.signup-form form a:hover{
+ text-decoration: underline;
+}
+
+.agree_code_of_conduct a {
+ color: #337ab7 !important;
+ text-decoration: none !important;
+}
\ No newline at end of file
diff --git a/assets/css/edit-client-public-keys.css b/assets/css/edit-client-public-keys.css
new file mode 100644
index 00000000..a28c9611
--- /dev/null
+++ b/assets/css/edit-client-public-keys.css
@@ -0,0 +1,28 @@
+.public-key-fingerprint {
+ color: #767676;
+ display: block;
+ font-weight: normal;
+}
+
+.public-key-validity-range {
+
+}
+
+.public-key-title {
+ display: block;
+ max-width: 360px;
+}
+
+.public-key-active {
+ background-color: #6cc644 !important;
+ box-shadow: 0 0 10px rgba(108, 198, 68, 0.5);
+ padding: 0 !important;
+ cursor: pointer;
+}
+
+.public-key-deactivated {
+ background-image: linear-gradient(#aaa, #ccc);
+ box-shadow: 0 1px 0 #fff;
+ padding: 0 !important;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/assets/css/edit-client.css b/assets/css/edit-client.css
new file mode 100644
index 00000000..f01ee61b
--- /dev/null
+++ b/assets/css/edit-client.css
@@ -0,0 +1,4 @@
+.copy-scopes-button{
+ color: black;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/assets/css/main.css b/assets/css/main.css
new file mode 100644
index 00000000..18278198
--- /dev/null
+++ b/assets/css/main.css
@@ -0,0 +1,372 @@
+body {
+
+}
+
+.header
+{
+ padding-top: 1em;
+}
+
+#logo img {
+ display: block;
+ max-width: 100px;
+ max-height: 54px;
+ margin: 0 auto;
+}
+
+.panel-heading {
+ background-color: #F5F5F5;
+ border-bottom: 1px solid #DDDDDD;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ margin: -15px -15px 15px;
+ padding: 10px 15px;
+}
+
+.panel {
+ background-color: #FFFFFF;
+ border: 1px solid #DDDDDD !important;
+ border-radius: 4px;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+ margin-bottom: 20px;
+ padding: 15px;
+}
+
+
+* {
+ -moz-box-sizing: border-box;
+}
+
+.margin-top-20 {
+ margin-top: 20px;
+}
+
+#redirect_uri, #origin{
+ margin-bottom: 0px!important;
+ height: 30px !important;
+ width: 90% !important;
+}
+
+#dialog-form-register-new-app textarea {
+ height: auto;
+}
+
+.label-client-secret{
+ font-size: 12px;
+ color: darkgray;
+ text-transform: uppercase;
+ font-weight: bold;
+}
+
+#client_id, #client_secret{
+ font-weight: normal;
+}
+
+.alert-error{
+ margin-top: 5px;
+ margin-bottom: 5px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+.popover-content{
+ font-size: 12px;
+}
+
+.popover {
+ width: auto;
+ max-width: 800px;
+}
+
+.info{
+ cursor: pointer;
+}
+
+.email-row{
+ margin-top: 5px;
+}
+
+.error-modal{
+ background-color: #F2DEDE !important;
+ border-color: #EED3D7 !important;
+ color: #B94A48 !important;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5)
+}
+
+.error-modal .modal-footer{
+ border-top: 1px solid #EED3D7 !important;
+ background-color: #F2DEDE !important;
+ border-color: #EED3D7 !important;
+ color: #B94A48 !important;
+ box-shadow: 0 1px 0 #EED3D7 inset !important;
+}
+
+.error-modal .modal-header{
+ border-bottom: 1px solid #EED3D7 !important;
+}
+
+.error-modal .alert-heading{
+ color: #B94A48;
+ margin: 0;
+}
+
+
+*::after, *::before {
+ box-sizing: border-box;
+}
+*::after, *::before {
+ box-sizing: border-box;
+}
+.bs-glyphicons li {
+ font-size: 12px;
+ width: 12.5%;
+}
+.bs-glyphicons li {
+ background-color: #f9f9f9;
+ border: 1px solid #fff;
+ float: left;
+ font-size: 10px;
+ height: 115px;
+ line-height: 1.4;
+ padding: 10px;
+ text-align: center;
+ width: 25%;
+}
+* {
+ box-sizing: border-box;
+}
+.bs-glyphicons-list {
+ list-style: outside none none;
+}
+body {
+ color: #333;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 14px;
+ line-height: 1.42857;
+}
+
+.accordion-toggle {
+ cursor: pointer;
+}
+
+.pointable {
+ cursor: pointer;
+ color: #000;
+}
+
+.del-client, .regenerate-client-secret, .del-allowed-uri, .revoke-access, .delete-public-key, .btn-delete {
+ color: #900 !important;
+ font-weight: bold !important;
+}
+
+.del-client:hover, .regenerate-client-secret:hover, .del-allowed-uri:hover, .revoke-access:hover, .delete-public-key:hover, .btn-delete:hover {
+ color: #fff !important;
+ background-color: #900 !important;
+ border-color: #d43f3a !important;
+}
+
+.panel-default > .panel-heading {
+ background-color: white !important;
+ border-color: #ddd !important;
+ color: #0078a3 !important;
+}
+
+.bootstrap-tagsinput{
+ width: 100% !important;
+}
+
+textarea {
+ resize: none;
+}
+
+.col-md-offset-neg-1 {
+ margin-left: -15%;
+}
+
+.clear-padding
+{
+ padding:0;
+}
+
+.formatted-credential
+{
+ text-align: left;
+}
+
+.auto-width
+{
+ width: auto !important;
+}
+
+.error-message
+{
+ color:#c40022;
+}
+
+.privacy-policy{
+ padding-top: 5px;
+}
+
+.alert{
+ margin-top: 5px;
+}
+
+.center{
+ text-align: center;
+}
+
+.bootstrap-tagsinput {
+ width: 100%;
+}
+
+.password-container > .progress {
+ margin-top: 5px;
+ font-weight: bold;
+ background-color: #B0BEC5 !important;
+}
+
+.password-container> .error-list
+{
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ font-size: 12px;
+}
+
+.password-container > .error-list li span::before
+{
+ font: normal normal normal 14px/1 FontAwesome;
+ content: "\f06a";
+ margin-right: 5px;
+}
+
+span.help-block
+{
+ font-size: 12px;
+ color: #d52929 !important;
+}
+
+span.help-block::before {
+ font: normal normal normal 14px/1 FontAwesome;
+ content: "\f06a";
+ margin-right: 5px;
+}
+
+.recaptcha-container{
+ text-align:center;
+}
+
+.recaptcha-container > .g-recaptcha {
+ display: inline-block;
+}
+
+/*Iphone 5*/
+@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : portrait) {
+ .recaptcha-container > .g-recaptcha {
+ display: inline-block;
+ transform: scale(0.66);
+ transform-origin: 0 0;
+ }
+}
+
+/*iPhone 6, 7, 8 in portrait*/
+@media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait) {
+ .recaptcha-container > .g-recaptcha {
+ display: inline-block;
+ transform: scale(0.85);
+ transform-origin: 0 0;
+ }
+}
+
+/* Pixel 2 */
+@media only screen and (min-device-width : 411px) and (max-device-width : 667px) and (orientation : portrait) {
+ .recaptcha-container > .g-recaptcha {
+ display: inline-block;
+ transform: scale(0.95);
+ transform-origin: 0 0;
+ }
+}
+
+.datepicker-dropdown{
+ z-index: 999999999999999999999 !important;
+}
+
+.add-item-button{
+ margin-bottom: 10px;
+}
+
+#search-term
+{
+ width: 40% !important;
+ height: 26px !important;
+ padding: 3px;
+
+}
+
+.delete-item{
+ color: #900 !important;
+ font-weight: bold !important;
+}
+
+#gender_specify{
+ margin-top: 10px;
+}
+
+.img-circle{
+ -webkit-tap-highlight-color: rgba(0,0,0,0);
+ line-height: 1.42857143;
+ color: #333;
+ font-size: 14px;
+ font-family: "Roboto",Helvetica,Arial,sans-serif;
+ -webkit-font-smoothing: antialiased;
+ font-weight: normal;
+ box-sizing: border-box;
+ border: 0;
+ vertical-align: middle;
+ border-radius: 50%;
+ height: 100px;
+ width: 100px;
+}
+
+.help-block{
+ -webkit-tap-highlight-color: rgba(0,0,0,0);
+ line-height: 1.42857143;
+ font-weight: 400;
+ font-family: "Roboto",Helvetica,Arial,sans-serif;
+ -webkit-font-smoothing: antialiased;
+ box-sizing: border-box;
+ margin: 0 0 10px;
+ display: block;
+ margin-top: 5px;
+ margin-bottom: 10px;
+ font-size: 12px;
+ color: #737373;
+}
+
+.input-group-button {
+ width: 1%;
+ vertical-align: middle;
+ display:table-cell;
+}
+
+.sort-header{
+ cursor: pointer;
+}
+
+.sort-header.current.desc:after{
+ content: '\f0dd';
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ margin:0px 0px 0px 10px;
+ text-decoration:none;
+}
+
+.sort-header.current.asc:after{
+ content: '\f0de';
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ margin:0px 0px 0px 10px;
+ text-decoration:none;
+}
diff --git a/assets/css/newClientDialog.css b/assets/css/newClientDialog.css
new file mode 100644
index 00000000..ba438b70
--- /dev/null
+++ b/assets/css/newClientDialog.css
@@ -0,0 +1,26 @@
+.vS4viHpYzLYWj40ztp9Q {
+ margin-top: 5%;
+ margin-bottom: 10%;
+ max-width: 1024px !important; }
+
+.si5JRn4stCvYE9qx9Zom {
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ color: deeppink; }
+
+.ug9xrtzzz3UCDP3FTjIu {
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ color: red; }
+
+.NxVGD5qVU1A06yWX5Gxz {
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ color: blue; }
+
+.ExrndYG7uxWUCO5J14j3 {
+ color: #0a1f31; }
+
diff --git a/assets/css/passwordReset.css b/assets/css/passwordReset.css
new file mode 100644
index 00000000..6fba5fa8
--- /dev/null
+++ b/assets/css/passwordReset.css
@@ -0,0 +1,36 @@
+.banner___jJTXB {
+ border: 2px solid #28A4DB;
+ margin-bottom: 40px;
+ color: #0a1f31; }
+ .banner___jJTXB .prepend_grid_item___sFCsI {
+ color: #EFF5F8;
+ background-color: #28A4DB; }
+ .banner___jJTXB .prepend_grid_item___sFCsI svg {
+ margin-left: auto;
+ margin-right: auto; }
+ .banner___jJTXB .append_grid_item___1zQAZ {
+ background-color: #EFF5F8;
+ padding: 10px 5px 10px 15px; }
+ .banner___jJTXB .append_grid_item___1zQAZ a {
+ color: #28A4DB; }
+
+.main_container___2ZFu- {
+ margin-top: 5%;
+ margin-bottom: 10%;
+ max-width: 544px !important; }
+ .main_container___2ZFu- .title_container___2OcN7 .app_logo___8kOOV {
+ display: flex;
+ max-width: 40%;
+ margin: auto; }
+ .main_container___2ZFu- .password_reset_container___3fObP {
+ border: solid 1px #e3e3e3;
+ color: #3fa2f7;
+ background-color: white; }
+ .main_container___2ZFu- .password_reset_container___3fObP .button___3h9z5 {
+ width: 380px;
+ color: white;
+ background-color: #3379b7;
+ margin-top: 10px; }
+
+
+/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9yZXNvdXJjZXMvanMvY29tcG9uZW50cy9iYW5uZXIvYmFubmVyLm1vZHVsZS5zY3NzIiwid2VicGFjazovLy8uL3Jlc291cmNlcy9qcy9wYXNzd29yZF9yZXNldC9wYXNzd29yZF9yZXNldC5tb2R1bGUuc2NzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQSxpQkFBaUI7QUFDakI7QUFDQTtBQUNBLDhCQUE4QjtBQUM5QjtBQUNBO0FBQ0EseUJBQXlCO0FBQ3pCO0FBQ0E7QUFDQSxnQ0FBZ0M7QUFDaEM7QUFDQSxxQkFBcUI7O0FDZHJCO0FBQ0E7QUFDQTtBQUNBLDhCQUE4QjtBQUM5QjtBQUNBO0FBQ0E7QUFDQSxpQkFBaUI7QUFDakI7QUFDQTtBQUNBO0FBQ0EsNEJBQTRCO0FBQzVCO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUJBQXVCIiwiZmlsZSI6Ii4vY3NzL3Bhc3N3b3JkUmVzZXQuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmJhbm5lcl9fX2pKVFhCIHtcbiAgYm9yZGVyOiAycHggc29saWQgIzI4QTREQjtcbiAgbWFyZ2luLWJvdHRvbTogNDBweDtcbiAgY29sb3I6ICMwYTFmMzE7IH1cbiAgLmJhbm5lcl9fX2pKVFhCIC5wcmVwZW5kX2dyaWRfaXRlbV9fX3NGQ3NJIHtcbiAgICBjb2xvcjogI0VGRjVGODtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMjhBNERCOyB9XG4gICAgLmJhbm5lcl9fX2pKVFhCIC5wcmVwZW5kX2dyaWRfaXRlbV9fX3NGQ3NJIHN2ZyB7XG4gICAgICBtYXJnaW4tbGVmdDogYXV0bztcbiAgICAgIG1hcmdpbi1yaWdodDogYXV0bzsgfVxuICAuYmFubmVyX19fakpUWEIgLmFwcGVuZF9ncmlkX2l0ZW1fX18xelFBWiB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogI0VGRjVGODtcbiAgICBwYWRkaW5nOiAxMHB4IDVweCAxMHB4IDE1cHg7IH1cbiAgICAuYmFubmVyX19fakpUWEIgLmFwcGVuZF9ncmlkX2l0ZW1fX18xelFBWiBhIHtcbiAgICAgIGNvbG9yOiAjMjhBNERCOyB9XG4iLCIubWFpbl9jb250YWluZXJfX18yWkZ1LSB7XG4gIG1hcmdpbi10b3A6IDUlO1xuICBtYXJnaW4tYm90dG9tOiAxMCU7XG4gIG1heC13aWR0aDogNTQ0cHggIWltcG9ydGFudDsgfVxuICAubWFpbl9jb250YWluZXJfX18yWkZ1LSAudGl0bGVfY29udGFpbmVyX19fMk9jTjcgLmFwcF9sb2dvX19fOGtPT1Yge1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgbWF4LXdpZHRoOiA0MCU7XG4gICAgbWFyZ2luOiBhdXRvOyB9XG4gIC5tYWluX2NvbnRhaW5lcl9fXzJaRnUtIC5wYXNzd29yZF9yZXNldF9jb250YWluZXJfX18zZk9iUCB7XG4gICAgYm9yZGVyOiBzb2xpZCAxcHggI2UzZTNlMztcbiAgICBjb2xvcjogIzNmYTJmNztcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB3aGl0ZTsgfVxuICAgIC5tYWluX2NvbnRhaW5lcl9fXzJaRnUtIC5wYXNzd29yZF9yZXNldF9jb250YWluZXJfX18zZk9iUCAuYnV0dG9uX19fM2g5ejUge1xuICAgICAgd2lkdGg6IDM4MHB4O1xuICAgICAgY29sb3I6IHdoaXRlO1xuICAgICAgYmFja2dyb3VuZC1jb2xvcjogIzMzNzliNztcbiAgICAgIG1hcmdpbi10b3A6IDEwcHg7IH1cbiJdLCJzb3VyY2VSb290IjoiIn0=*/
\ No newline at end of file
diff --git a/assets/css/private-keys.css b/assets/css/private-keys.css
new file mode 100644
index 00000000..9d70596f
--- /dev/null
+++ b/assets/css/private-keys.css
@@ -0,0 +1,30 @@
+.private-key-fingerprint {
+ color: #767676;
+ display: block;
+ font-weight: normal;
+}
+
+.private-key-validity-range {
+
+}
+
+.private-key-title {
+ display: block;
+ max-width: 360px;
+}
+
+.private-key-active {
+ background-color: #6cc644 !important;
+ box-shadow: 0 0 10px rgba(108, 198, 68, 0.5);
+ padding: 0 !important;
+ cursor: pointer;
+}
+
+.private-key-deactivated {
+ background-image: linear-gradient(#aaa, #ccc);
+ box-shadow: 0 1px 0 #fff;
+ padding: 0 !important;
+ cursor: pointer;
+}
+
+#ui-datepicker-div {z-index:9999 !important;}
diff --git a/assets/css/smoothness/images/animated-overlay.gif b/assets/css/smoothness/images/animated-overlay.gif
new file mode 100755
index 00000000..d441f75e
Binary files /dev/null and b/assets/css/smoothness/images/animated-overlay.gif differ
diff --git a/assets/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png b/assets/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png
new file mode 100755
index 00000000..c09235f6
Binary files /dev/null and b/assets/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png differ
diff --git a/assets/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png b/assets/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png
new file mode 100755
index 00000000..d29011d2
Binary files /dev/null and b/assets/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png differ
diff --git a/assets/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png b/assets/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png
new file mode 100755
index 00000000..1558bea5
Binary files /dev/null and b/assets/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png differ
diff --git a/assets/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png b/assets/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100755
index 00000000..0359b121
Binary files /dev/null and b/assets/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/assets/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png b/assets/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png
new file mode 100755
index 00000000..4fce6c46
Binary files /dev/null and b/assets/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png differ
diff --git a/assets/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png b/assets/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100755
index 00000000..5299b5a0
Binary files /dev/null and b/assets/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png differ
diff --git a/assets/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png b/assets/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png
new file mode 100755
index 00000000..398c56a9
Binary files /dev/null and b/assets/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png differ
diff --git a/assets/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/assets/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png
new file mode 100755
index 00000000..d819aa58
Binary files /dev/null and b/assets/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ
diff --git a/assets/css/smoothness/images/ui-icons_222222_256x240.png b/assets/css/smoothness/images/ui-icons_222222_256x240.png
new file mode 100755
index 00000000..c1cb1170
Binary files /dev/null and b/assets/css/smoothness/images/ui-icons_222222_256x240.png differ
diff --git a/assets/css/smoothness/images/ui-icons_2e83ff_256x240.png b/assets/css/smoothness/images/ui-icons_2e83ff_256x240.png
new file mode 100755
index 00000000..84b601bf
Binary files /dev/null and b/assets/css/smoothness/images/ui-icons_2e83ff_256x240.png differ
diff --git a/assets/css/smoothness/images/ui-icons_454545_256x240.png b/assets/css/smoothness/images/ui-icons_454545_256x240.png
new file mode 100755
index 00000000..b6db1acd
Binary files /dev/null and b/assets/css/smoothness/images/ui-icons_454545_256x240.png differ
diff --git a/assets/css/smoothness/images/ui-icons_888888_256x240.png b/assets/css/smoothness/images/ui-icons_888888_256x240.png
new file mode 100755
index 00000000..feea0e20
Binary files /dev/null and b/assets/css/smoothness/images/ui-icons_888888_256x240.png differ
diff --git a/assets/css/smoothness/images/ui-icons_cd0a0a_256x240.png b/assets/css/smoothness/images/ui-icons_cd0a0a_256x240.png
new file mode 100755
index 00000000..ed5b6b09
Binary files /dev/null and b/assets/css/smoothness/images/ui-icons_cd0a0a_256x240.png differ
diff --git a/assets/css/smoothness/jquery-ui-1.10.3.custom.css b/assets/css/smoothness/jquery-ui-1.10.3.custom.css
new file mode 100755
index 00000000..893090f6
--- /dev/null
+++ b/assets/css/smoothness/jquery-ui-1.10.3.custom.css
@@ -0,0 +1,1177 @@
+/*! jQuery UI - v1.10.3 - 2013-12-11
+* http://jqueryui.com
+* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden {
+ display: none;
+}
+.ui-helper-hidden-accessible {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+.ui-helper-reset {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ line-height: 1.3;
+ text-decoration: none;
+ font-size: 100%;
+ list-style: none;
+}
+.ui-helper-clearfix:before,
+.ui-helper-clearfix:after {
+ content: "";
+ display: table;
+ border-collapse: collapse;
+}
+.ui-helper-clearfix:after {
+ clear: both;
+}
+.ui-helper-clearfix {
+ min-height: 0; /* support: IE7 */
+}
+.ui-helper-zfix {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ position: absolute;
+ opacity: 0;
+ filter:Alpha(Opacity=0);
+}
+
+.ui-front {
+ z-index: 100;
+}
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled {
+ cursor: default !important;
+}
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+ display: block;
+ text-indent: -99999px;
+ overflow: hidden;
+ background-repeat: no-repeat;
+}
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.ui-resizable {
+ position: relative;
+}
+.ui-resizable-handle {
+ position: absolute;
+ font-size: 0.1px;
+ display: block;
+}
+.ui-resizable-disabled .ui-resizable-handle,
+.ui-resizable-autohide .ui-resizable-handle {
+ display: none;
+}
+.ui-resizable-n {
+ cursor: n-resize;
+ height: 7px;
+ width: 100%;
+ top: -5px;
+ left: 0;
+}
+.ui-resizable-s {
+ cursor: s-resize;
+ height: 7px;
+ width: 100%;
+ bottom: -5px;
+ left: 0;
+}
+.ui-resizable-e {
+ cursor: e-resize;
+ width: 7px;
+ right: -5px;
+ top: 0;
+ height: 100%;
+}
+.ui-resizable-w {
+ cursor: w-resize;
+ width: 7px;
+ left: -5px;
+ top: 0;
+ height: 100%;
+}
+.ui-resizable-se {
+ cursor: se-resize;
+ width: 12px;
+ height: 12px;
+ right: 1px;
+ bottom: 1px;
+}
+.ui-resizable-sw {
+ cursor: sw-resize;
+ width: 9px;
+ height: 9px;
+ left: -5px;
+ bottom: -5px;
+}
+.ui-resizable-nw {
+ cursor: nw-resize;
+ width: 9px;
+ height: 9px;
+ left: -5px;
+ top: -5px;
+}
+.ui-resizable-ne {
+ cursor: ne-resize;
+ width: 9px;
+ height: 9px;
+ right: -5px;
+ top: -5px;
+}
+.ui-selectable-helper {
+ position: absolute;
+ z-index: 100;
+ border: 1px dotted black;
+}
+.ui-accordion .ui-accordion-header {
+ display: block;
+ cursor: pointer;
+ position: relative;
+ margin-top: 2px;
+ padding: .5em .5em .5em .7em;
+ min-height: 0; /* support: IE7 */
+}
+.ui-accordion .ui-accordion-icons {
+ padding-left: 2.2em;
+}
+.ui-accordion .ui-accordion-noicons {
+ padding-left: .7em;
+}
+.ui-accordion .ui-accordion-icons .ui-accordion-icons {
+ padding-left: 2.2em;
+}
+.ui-accordion .ui-accordion-header .ui-accordion-header-icon {
+ position: absolute;
+ left: .5em;
+ top: 50%;
+ margin-top: -8px;
+}
+.ui-accordion .ui-accordion-content {
+ padding: 1em 2.2em;
+ border-top: 0;
+ overflow: auto;
+}
+.ui-autocomplete {
+ position: absolute;
+ top: 0;
+ left: 0;
+ cursor: default;
+}
+.ui-button {
+ display: inline-block;
+ position: relative;
+ padding: 0;
+ line-height: normal;
+ margin-right: .1em;
+ cursor: pointer;
+ vertical-align: middle;
+ text-align: center;
+ overflow: visible; /* removes extra width in IE */
+}
+.ui-button,
+.ui-button:link,
+.ui-button:visited,
+.ui-button:hover,
+.ui-button:active {
+ text-decoration: none;
+}
+/* to make room for the icon, a width needs to be set here */
+.ui-button-icon-only {
+ width: 2.2em;
+}
+/* button elements seem to need a little more width */
+button.ui-button-icon-only {
+ width: 2.4em;
+}
+.ui-button-icons-only {
+ width: 3.4em;
+}
+button.ui-button-icons-only {
+ width: 3.7em;
+}
+
+/* button text element */
+.ui-button .ui-button-text {
+ display: block;
+ line-height: normal;
+}
+.ui-button-text-only .ui-button-text {
+ padding: .4em 1em;
+}
+.ui-button-icon-only .ui-button-text,
+.ui-button-icons-only .ui-button-text {
+ padding: .4em;
+ text-indent: -9999999px;
+}
+.ui-button-text-icon-primary .ui-button-text,
+.ui-button-text-icons .ui-button-text {
+ padding: .4em 1em .4em 2.1em;
+}
+.ui-button-text-icon-secondary .ui-button-text,
+.ui-button-text-icons .ui-button-text {
+ padding: .4em 2.1em .4em 1em;
+}
+.ui-button-text-icons .ui-button-text {
+ padding-left: 2.1em;
+ padding-right: 2.1em;
+}
+/* no icon support for input elements, provide padding by default */
+input.ui-button {
+ padding: .4em 1em;
+}
+
+/* button icon element(s) */
+.ui-button-icon-only .ui-icon,
+.ui-button-text-icon-primary .ui-icon,
+.ui-button-text-icon-secondary .ui-icon,
+.ui-button-text-icons .ui-icon,
+.ui-button-icons-only .ui-icon {
+ position: absolute;
+ top: 50%;
+ margin-top: -8px;
+}
+.ui-button-icon-only .ui-icon {
+ left: 50%;
+ margin-left: -8px;
+}
+.ui-button-text-icon-primary .ui-button-icon-primary,
+.ui-button-text-icons .ui-button-icon-primary,
+.ui-button-icons-only .ui-button-icon-primary {
+ left: .5em;
+}
+.ui-button-text-icon-secondary .ui-button-icon-secondary,
+.ui-button-text-icons .ui-button-icon-secondary,
+.ui-button-icons-only .ui-button-icon-secondary {
+ right: .5em;
+}
+
+/* button sets */
+.ui-buttonset {
+ margin-right: 7px;
+}
+.ui-buttonset .ui-button {
+ margin-left: 0;
+ margin-right: -.3em;
+}
+
+/* workarounds */
+/* reset extra padding in Firefox, see h5bp.com/l */
+input.ui-button::-moz-focus-inner,
+button.ui-button::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+.ui-datepicker {
+ width: 17em;
+ padding: .2em .2em 0;
+ display: none;
+}
+.ui-datepicker .ui-datepicker-header {
+ position: relative;
+ padding: .2em 0;
+}
+.ui-datepicker .ui-datepicker-prev,
+.ui-datepicker .ui-datepicker-next {
+ position: absolute;
+ top: 2px;
+ width: 1.8em;
+ height: 1.8em;
+}
+.ui-datepicker .ui-datepicker-prev-hover,
+.ui-datepicker .ui-datepicker-next-hover {
+ top: 1px;
+}
+.ui-datepicker .ui-datepicker-prev {
+ left: 2px;
+}
+.ui-datepicker .ui-datepicker-next {
+ right: 2px;
+}
+.ui-datepicker .ui-datepicker-prev-hover {
+ left: 1px;
+}
+.ui-datepicker .ui-datepicker-next-hover {
+ right: 1px;
+}
+.ui-datepicker .ui-datepicker-prev span,
+.ui-datepicker .ui-datepicker-next span {
+ display: block;
+ position: absolute;
+ left: 50%;
+ margin-left: -8px;
+ top: 50%;
+ margin-top: -8px;
+}
+.ui-datepicker .ui-datepicker-title {
+ margin: 0 2.3em;
+ line-height: 1.8em;
+ text-align: center;
+}
+.ui-datepicker .ui-datepicker-title select {
+ font-size: 1em;
+ margin: 1px 0;
+}
+.ui-datepicker select.ui-datepicker-month-year {
+ width: 100%;
+}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year {
+ width: 49%;
+}
+.ui-datepicker table {
+ width: 100%;
+ font-size: .9em;
+ border-collapse: collapse;
+ margin: 0 0 .4em;
+}
+.ui-datepicker th {
+ padding: .7em .3em;
+ text-align: center;
+ font-weight: bold;
+ border: 0;
+}
+.ui-datepicker td {
+ border: 0;
+ padding: 1px;
+}
+.ui-datepicker td span,
+.ui-datepicker td a {
+ display: block;
+ padding: .2em;
+ text-align: right;
+ text-decoration: none;
+}
+.ui-datepicker .ui-datepicker-buttonpane {
+ background-image: none;
+ margin: .7em 0 0 0;
+ padding: 0 .2em;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 0;
+}
+.ui-datepicker .ui-datepicker-buttonpane button {
+ float: right;
+ margin: .5em .2em .4em;
+ cursor: pointer;
+ padding: .2em .6em .3em .6em;
+ width: auto;
+ overflow: visible;
+}
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
+ float: left;
+}
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi {
+ width: auto;
+}
+.ui-datepicker-multi .ui-datepicker-group {
+ float: left;
+}
+.ui-datepicker-multi .ui-datepicker-group table {
+ width: 95%;
+ margin: 0 auto .4em;
+}
+.ui-datepicker-multi-2 .ui-datepicker-group {
+ width: 50%;
+}
+.ui-datepicker-multi-3 .ui-datepicker-group {
+ width: 33.3%;
+}
+.ui-datepicker-multi-4 .ui-datepicker-group {
+ width: 25%;
+}
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
+ border-left-width: 0;
+}
+.ui-datepicker-multi .ui-datepicker-buttonpane {
+ clear: left;
+}
+.ui-datepicker-row-break {
+ clear: both;
+ width: 100%;
+ font-size: 0;
+}
+
+/* RTL support */
+.ui-datepicker-rtl {
+ direction: rtl;
+}
+.ui-datepicker-rtl .ui-datepicker-prev {
+ right: 2px;
+ left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next {
+ left: 2px;
+ right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-prev:hover {
+ right: 1px;
+ left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next:hover {
+ left: 1px;
+ right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane {
+ clear: right;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button {
+ float: left;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
+.ui-datepicker-rtl .ui-datepicker-group {
+ float: right;
+}
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
+ border-right-width: 0;
+ border-left-width: 1px;
+}
+.ui-dialog {
+ position: absolute;
+ top: 0;
+ left: 0;
+ padding: .2em;
+ outline: 0;
+}
+.ui-dialog .ui-dialog-titlebar {
+ padding: .4em 1em;
+ position: relative;
+}
+.ui-dialog .ui-dialog-title {
+ float: left;
+ margin: .1em 0;
+ white-space: nowrap;
+ width: 90%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.ui-dialog .ui-dialog-titlebar-close {
+ position: absolute;
+ right: .3em;
+ top: 50%;
+ width: 21px;
+ margin: -10px 0 0 0;
+ padding: 1px;
+ height: 20px;
+}
+.ui-dialog .ui-dialog-content {
+ position: relative;
+ border: 0;
+ padding: .5em 1em;
+ background: none;
+ overflow: auto;
+}
+.ui-dialog .ui-dialog-buttonpane {
+ text-align: left;
+ border-width: 1px 0 0 0;
+ background-image: none;
+ margin-top: .5em;
+ padding: .3em 1em .5em .4em;
+}
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
+ float: right;
+}
+.ui-dialog .ui-dialog-buttonpane button {
+ margin: .5em .4em .5em 0;
+ cursor: pointer;
+}
+.ui-dialog .ui-resizable-se {
+ width: 12px;
+ height: 12px;
+ right: -5px;
+ bottom: -5px;
+ background-position: 16px 16px;
+}
+.ui-draggable .ui-dialog-titlebar {
+ cursor: move;
+}
+.ui-menu {
+ list-style: none;
+ padding: 2px;
+ margin: 0;
+ display: block;
+ outline: none;
+}
+.ui-menu .ui-menu {
+ margin-top: -3px;
+ position: absolute;
+}
+.ui-menu .ui-menu-item {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ /* support: IE10, see #8844 */
+ list-style-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);
+}
+.ui-menu .ui-menu-divider {
+ margin: 5px -2px 5px -2px;
+ height: 0;
+ font-size: 0;
+ line-height: 0;
+ border-width: 1px 0 0 0;
+}
+.ui-menu .ui-menu-item a {
+ text-decoration: none;
+ display: block;
+ padding: 2px .4em;
+ line-height: 1.5;
+ min-height: 0; /* support: IE7 */
+ font-weight: normal;
+}
+.ui-menu .ui-menu-item a.ui-state-focus,
+.ui-menu .ui-menu-item a.ui-state-active {
+ font-weight: normal;
+ margin: -1px;
+}
+
+.ui-menu .ui-state-disabled {
+ font-weight: normal;
+ margin: .4em 0 .2em;
+ line-height: 1.5;
+}
+.ui-menu .ui-state-disabled a {
+ cursor: default;
+}
+
+/* icon support */
+.ui-menu-icons {
+ position: relative;
+}
+.ui-menu-icons .ui-menu-item a {
+ position: relative;
+ padding-left: 2em;
+}
+
+/* left-aligned */
+.ui-menu .ui-icon {
+ position: absolute;
+ top: .2em;
+ left: .2em;
+}
+
+/* right-aligned */
+.ui-menu .ui-menu-icon {
+ position: static;
+ float: right;
+}
+.ui-progressbar {
+ height: 2em;
+ text-align: left;
+ overflow: hidden;
+}
+.ui-progressbar .ui-progressbar-value {
+ margin: -1px;
+ height: 100%;
+}
+.ui-progressbar .ui-progressbar-overlay {
+ background: url("images/animated-overlay.gif");
+ height: 100%;
+ filter: alpha(opacity=25);
+ opacity: 0.25;
+}
+.ui-progressbar-indeterminate .ui-progressbar-value {
+ background-image: none;
+}
+.ui-slider {
+ position: relative;
+ text-align: left;
+}
+.ui-slider .ui-slider-handle {
+ position: absolute;
+ z-index: 2;
+ width: 1.2em;
+ height: 1.2em;
+ cursor: default;
+}
+.ui-slider .ui-slider-range {
+ position: absolute;
+ z-index: 1;
+ font-size: .7em;
+ display: block;
+ border: 0;
+ background-position: 0 0;
+}
+
+/* For IE8 - See #6727 */
+.ui-slider.ui-state-disabled .ui-slider-handle,
+.ui-slider.ui-state-disabled .ui-slider-range {
+ filter: inherit;
+}
+
+.ui-slider-horizontal {
+ height: .8em;
+}
+.ui-slider-horizontal .ui-slider-handle {
+ top: -.3em;
+ margin-left: -.6em;
+}
+.ui-slider-horizontal .ui-slider-range {
+ top: 0;
+ height: 100%;
+}
+.ui-slider-horizontal .ui-slider-range-min {
+ left: 0;
+}
+.ui-slider-horizontal .ui-slider-range-max {
+ right: 0;
+}
+
+.ui-slider-vertical {
+ width: .8em;
+ height: 100px;
+}
+.ui-slider-vertical .ui-slider-handle {
+ left: -.3em;
+ margin-left: 0;
+ margin-bottom: -.6em;
+}
+.ui-slider-vertical .ui-slider-range {
+ left: 0;
+ width: 100%;
+}
+.ui-slider-vertical .ui-slider-range-min {
+ bottom: 0;
+}
+.ui-slider-vertical .ui-slider-range-max {
+ top: 0;
+}
+.ui-spinner {
+ position: relative;
+ display: inline-block;
+ overflow: hidden;
+ padding: 0;
+ vertical-align: middle;
+}
+.ui-spinner-input {
+ border: none;
+ background: none;
+ color: inherit;
+ padding: 0;
+ margin: .2em 0;
+ vertical-align: middle;
+ margin-left: .4em;
+ margin-right: 22px;
+}
+.ui-spinner-button {
+ width: 16px;
+ height: 50%;
+ font-size: .5em;
+ padding: 0;
+ margin: 0;
+ text-align: center;
+ position: absolute;
+ cursor: default;
+ display: block;
+ overflow: hidden;
+ right: 0;
+}
+/* more specificity required here to overide default borders */
+.ui-spinner a.ui-spinner-button {
+ border-top: none;
+ border-bottom: none;
+ border-right: none;
+}
+/* vertical centre icon */
+.ui-spinner .ui-icon {
+ position: absolute;
+ margin-top: -8px;
+ top: 50%;
+ left: 0;
+}
+.ui-spinner-up {
+ top: 0;
+}
+.ui-spinner-down {
+ bottom: 0;
+}
+
+/* TR overrides */
+.ui-spinner .ui-icon-triangle-1-s {
+ /* need to fix icons sprite */
+ background-position: -65px -16px;
+}
+.ui-tabs {
+ position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+ padding: .2em;
+}
+.ui-tabs .ui-tabs-nav {
+ margin: 0;
+ padding: .2em .2em 0;
+}
+.ui-tabs .ui-tabs-nav li {
+ list-style: none;
+ float: left;
+ position: relative;
+ top: 0;
+ margin: 1px .2em 0 0;
+ border-bottom-width: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+.ui-tabs .ui-tabs-nav li a {
+ float: left;
+ padding: .5em 1em;
+ text-decoration: none;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active {
+ margin-bottom: -1px;
+ padding-bottom: 1px;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active a,
+.ui-tabs .ui-tabs-nav li.ui-state-disabled a,
+.ui-tabs .ui-tabs-nav li.ui-tabs-loading a {
+ cursor: text;
+}
+.ui-tabs .ui-tabs-nav li a, /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a {
+ cursor: pointer;
+}
+.ui-tabs .ui-tabs-panel {
+ display: block;
+ border-width: 0;
+ padding: 1em 1.4em;
+ background: none;
+}
+.ui-tooltip {
+ padding: 8px;
+ position: absolute;
+ z-index: 9999;
+ max-width: 300px;
+ -webkit-box-shadow: 0 0 5px #aaa;
+ box-shadow: 0 0 5px #aaa;
+}
+body .ui-tooltip {
+ border-width: 2px;
+}
+
+/* Component containers
+----------------------------------*/
+.ui-widget {
+ font-family: Verdana,Arial,sans-serif;
+ font-size: 1.1em;
+}
+.ui-widget .ui-widget {
+ font-size: 1em;
+}
+.ui-widget input,
+.ui-widget select,
+.ui-widget textarea,
+.ui-widget button {
+ font-family: Verdana,Arial,sans-serif;
+ font-size: 1em;
+}
+.ui-widget-content {
+ border: 1px solid #aaaaaa;
+ background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;
+ color: #222222;
+}
+.ui-widget-content a {
+ color: #222222;
+}
+.ui-widget-header {
+ border: 1px solid #aaaaaa;
+ background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;
+ color: #222222;
+ font-weight: bold;
+}
+.ui-widget-header a {
+ color: #222222;
+}
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default,
+.ui-widget-content .ui-state-default,
+.ui-widget-header .ui-state-default {
+ border: 1px solid #d3d3d3;
+ background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;
+ font-weight: normal;
+ color: #555555;
+}
+.ui-state-default a,
+.ui-state-default a:link,
+.ui-state-default a:visited {
+ color: #555555;
+ text-decoration: none;
+}
+.ui-state-hover,
+.ui-widget-content .ui-state-hover,
+.ui-widget-header .ui-state-hover,
+.ui-state-focus,
+.ui-widget-content .ui-state-focus,
+.ui-widget-header .ui-state-focus {
+ border: 1px solid #999999;
+ background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;
+ font-weight: normal;
+ color: #212121;
+}
+.ui-state-hover a,
+.ui-state-hover a:hover,
+.ui-state-hover a:link,
+.ui-state-hover a:visited {
+ color: #212121;
+ text-decoration: none;
+}
+.ui-state-active,
+.ui-widget-content .ui-state-active,
+.ui-widget-header .ui-state-active {
+ border: 1px solid #aaaaaa;
+ background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;
+ font-weight: normal;
+ color: #212121;
+}
+.ui-state-active a,
+.ui-state-active a:link,
+.ui-state-active a:visited {
+ color: #212121;
+ text-decoration: none;
+}
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight,
+.ui-widget-content .ui-state-highlight,
+.ui-widget-header .ui-state-highlight {
+ border: 1px solid #fcefa1;
+ background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x;
+ color: #363636;
+}
+.ui-state-highlight a,
+.ui-widget-content .ui-state-highlight a,
+.ui-widget-header .ui-state-highlight a {
+ color: #363636;
+}
+.ui-state-error,
+.ui-widget-content .ui-state-error,
+.ui-widget-header .ui-state-error {
+ border: 1px solid #cd0a0a;
+ background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;
+ color: #cd0a0a;
+}
+.ui-state-error a,
+.ui-widget-content .ui-state-error a,
+.ui-widget-header .ui-state-error a {
+ color: #cd0a0a;
+}
+.ui-state-error-text,
+.ui-widget-content .ui-state-error-text,
+.ui-widget-header .ui-state-error-text {
+ color: #cd0a0a;
+}
+.ui-priority-primary,
+.ui-widget-content .ui-priority-primary,
+.ui-widget-header .ui-priority-primary {
+ font-weight: bold;
+}
+.ui-priority-secondary,
+.ui-widget-content .ui-priority-secondary,
+.ui-widget-header .ui-priority-secondary {
+ opacity: .7;
+ filter:Alpha(Opacity=70);
+ font-weight: normal;
+}
+.ui-state-disabled,
+.ui-widget-content .ui-state-disabled,
+.ui-widget-header .ui-state-disabled {
+ opacity: .35;
+ filter:Alpha(Opacity=35);
+ background-image: none;
+}
+.ui-state-disabled .ui-icon {
+ filter:Alpha(Opacity=35); /* For IE8 - See #6059 */
+}
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+ width: 16px;
+ height: 16px;
+}
+.ui-icon,
+.ui-widget-content .ui-icon {
+ background-image: url(images/ui-icons_222222_256x240.png);
+}
+.ui-widget-header .ui-icon {
+ background-image: url(images/ui-icons_222222_256x240.png);
+}
+.ui-state-default .ui-icon {
+ background-image: url(images/ui-icons_888888_256x240.png);
+}
+.ui-state-hover .ui-icon,
+.ui-state-focus .ui-icon {
+ background-image: url(images/ui-icons_454545_256x240.png);
+}
+.ui-state-active .ui-icon {
+ background-image: url(images/ui-icons_454545_256x240.png);
+}
+.ui-state-highlight .ui-icon {
+ background-image: url(images/ui-icons_2e83ff_256x240.png);
+}
+.ui-state-error .ui-icon,
+.ui-state-error-text .ui-icon {
+ background-image: url(images/ui-icons_cd0a0a_256x240.png);
+}
+
+/* positioning */
+.ui-icon-blank { background-position: 16px 16px; }
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-left,
+.ui-corner-tl {
+ border-top-left-radius: 4px;
+}
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-right,
+.ui-corner-tr {
+ border-top-right-radius: 4px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-left,
+.ui-corner-bl {
+ border-bottom-left-radius: 4px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-right,
+.ui-corner-br {
+ border-bottom-right-radius: 4px;
+}
+
+/* Overlays */
+.ui-widget-overlay {
+ background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;
+ opacity: .3;
+ filter: Alpha(Opacity=30);
+}
+.ui-widget-shadow {
+ margin: -8px 0 0 -8px;
+ padding: 8px;
+ background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;
+ opacity: .3;
+ filter: Alpha(Opacity=30);
+ border-radius: 8px;
+}
diff --git a/assets/css/smoothness/jquery-ui-1.10.3.custom.min.css b/assets/css/smoothness/jquery-ui-1.10.3.custom.min.css
new file mode 100755
index 00000000..daebf8db
--- /dev/null
+++ b/assets/css/smoothness/jquery-ui-1.10.3.custom.min.css
@@ -0,0 +1,7 @@
+/*! jQuery UI - v1.10.3 - 2013-12-11
+* http://jqueryui.com
+* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
+
+.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;min-height:0}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:21px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("images/animated-overlay.gif");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-tabs-loading a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_888888_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png)}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px}
\ No newline at end of file
diff --git a/assets/img/apis/server.png b/assets/img/apis/server.png
new file mode 100644
index 00000000..22df96b2
Binary files /dev/null and b/assets/img/apis/server.png differ
diff --git a/assets/img/generic-profile-photo-small.png b/assets/img/generic-profile-photo-small.png
new file mode 100755
index 00000000..029d3e1a
Binary files /dev/null and b/assets/img/generic-profile-photo-small.png differ
diff --git a/assets/img/generic-profile-photo.png b/assets/img/generic-profile-photo.png
new file mode 100755
index 00000000..2fcfed82
Binary files /dev/null and b/assets/img/generic-profile-photo.png differ
diff --git a/assets/img/glyphicons-halflings-white.png b/assets/img/glyphicons-halflings-white.png
new file mode 100644
index 00000000..3bf6484a
Binary files /dev/null and b/assets/img/glyphicons-halflings-white.png differ
diff --git a/assets/img/glyphicons-halflings.png b/assets/img/glyphicons-halflings.png
new file mode 100644
index 00000000..a9969993
Binary files /dev/null and b/assets/img/glyphicons-halflings.png differ
diff --git a/assets/img/oauth2.default.logo.png b/assets/img/oauth2.default.logo.png
new file mode 100644
index 00000000..ee2aab22
Binary files /dev/null and b/assets/img/oauth2.default.logo.png differ
diff --git a/assets/img/openstack-logo-full.svg b/assets/img/openstack-logo-full.svg
new file mode 100644
index 00000000..a2f42a10
--- /dev/null
+++ b/assets/img/openstack-logo-full.svg
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/img/third_party_identity_providers/icon_apple.svg b/assets/img/third_party_identity_providers/icon_apple.svg
new file mode 100644
index 00000000..b3ab05b7
--- /dev/null
+++ b/assets/img/third_party_identity_providers/icon_apple.svg
@@ -0,0 +1,56 @@
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
diff --git a/assets/img/third_party_identity_providers/icon_facebook.svg b/assets/img/third_party_identity_providers/icon_facebook.svg
new file mode 100644
index 00000000..4619e911
--- /dev/null
+++ b/assets/img/third_party_identity_providers/icon_facebook.svg
@@ -0,0 +1,13 @@
+
+
+
+ Fill 1
+ Created with Sketch.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/img/third_party_identity_providers/icon_google.png b/assets/img/third_party_identity_providers/icon_google.png
new file mode 100644
index 00000000..ae7f0451
Binary files /dev/null and b/assets/img/third_party_identity_providers/icon_google.png differ
diff --git a/assets/img/third_party_identity_providers/icon_linkedin.svg b/assets/img/third_party_identity_providers/icon_linkedin.svg
new file mode 100644
index 00000000..d420aecf
--- /dev/null
+++ b/assets/img/third_party_identity_providers/icon_linkedin.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/assets/img/third_party_identity_providers/icon_okta.png b/assets/img/third_party_identity_providers/icon_okta.png
new file mode 100644
index 00000000..550b8d95
Binary files /dev/null and b/assets/img/third_party_identity_providers/icon_okta.png differ
diff --git a/assets/js/admin/banned-ips.js b/assets/js/admin/banned-ips.js
new file mode 100644
index 00000000..1ac91307
--- /dev/null
+++ b/assets/js/admin/banned-ips.js
@@ -0,0 +1,42 @@
+jQuery(document).ready(function($){
+
+ $('#server-admin','#main-menu').addClass('active');
+
+ if($('#ips-table tr').length===1){
+ $('#ips-info').show();
+ $('#ips-table').hide();
+ }
+ else{
+ $('#ips-info').hide();
+ $('#ips-table').show();
+ }
+
+ $("body").on('click',".revoke-ip",function(event){
+ if(confirm("Are you sure that you want to revoke this banned ip?")){
+ var url = $(this).attr('href');
+ var ip_id = $(this).attr('data-ip-id');
+ $.ajax(
+ {
+ type: "DELETE",
+ url: url,
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ $('#'+ip_id,'#body-ips').remove();
+
+ if($('#ips-table tr').length===1){
+ $('#ips-info').show();
+ $('#ips-table').hide();
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+});
\ No newline at end of file
diff --git a/assets/js/admin/edit-group.js b/assets/js/admin/edit-group.js
new file mode 100644
index 00000000..cdbd321e
--- /dev/null
+++ b/assets/js/admin/edit-group.js
@@ -0,0 +1,194 @@
+// constructor
+function GroupMembersCrud(urls, perPage) {
+ var actions = 'Remove ';
+ this.urls = urls;
+ var _this = this;
+ var templatePage = $('
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' + actions + ' ' +
+ ' ');
+
+ var directivesPage = {
+ 'tr': {
+ 'user<-context': {
+ 'td.user-id': 'user.id',
+ 'td.user-fullname': function(arg){
+ return arg.item.first_name+' '+arg.item.last_name;
+ },
+ 'td.user-email': 'user.email',
+ 'a.delete-item@href': function (arg) {
+ var id = arg.item.id;
+ var href = _this.urls.delete;
+ return href.replace('@id', id);
+ },
+ }
+ }
+ };
+
+ // Chain constructor with call
+ BasicCrud.call(this, urls, perPage, templatePage, directivesPage, {
+ 'delete_item.title': 'Are you sure to Remove this User from Group?',
+ 'delete_item.text': 'This is an non reversible process!',
+ });
+}
+
+GroupMembersCrud.prototype = Object.create(BasicCrud.prototype);
+GroupMembersCrud.prototype.constructor = GroupMembersCrud;
+
+GroupMembersCrud.prototype._buildFilters = function () {
+ var term = encodeURIComponent(this.searchTerm);
+ return 'filter=first_name=@'+term+',last_name=@'+term+',email=@'+term+',full_name=@'+term;
+};
+
+GroupMembersCrud.prototype.init = function () {
+ BasicCrud.prototype.init.call(this);
+ var _this = this;
+ jQuery(document).ready(function ($) {
+
+ var users = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: urls.fetchUsers,
+ wildcard: '%QUERY%',
+ prepare: function (query, settings) {
+ var term = encodeURIComponent(query);
+ settings.url = urls.fetchUsers+'?filter=first_name=@'+term+',last_name=@'+term+',email=@'+term+',full_name=@'+term;
+ return settings;
+ },
+ transform: function(input){
+ var page = input.data;
+ return page;
+ }
+ }
+ });
+
+ $("body").on('click', "#btn-add-user", function (event) {
+ var newUserId = $('#add-user').val();
+ if(newUserId != '') {
+ $('#add-user').val('');
+ $('#add-user').tagsinput('removeAll');
+ handlePlaceHolder();
+ var url = _this.urls.add.replace('@id', newUserId);
+ $.ajax(
+ {
+ type: "PUT",
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ _this.loadPage();
+
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+
+ $('#add-user').tagsinput({
+ maxTags: 1,
+ itemValue: function(item) {
+ return item.id;
+ },
+ itemText: function(item) {
+ return item.first_name + ' ' + item.last_name+' ('+item.email+')' ;
+ },
+ freeInput: false,
+ allowDuplicates: false,
+ typeaheadjs: [
+ {
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'users',
+ display: function(item) {
+ return item.first_name + ' ' + item.last_name+' ('+item.email+')' ;
+ },
+ templates: {
+ suggestion: function (item) {
+ return '' + item.first_name + ' ' + item.last_name +' ('+item.email+')
';
+ }
+ },
+ source: users,
+ limit: 10
+ }
+ ]
+ });
+
+ function handlePlaceHolder()
+ {
+ if($('#add-user').val())
+ {
+ $('.bootstrap-tagsinput input').attr('placeholder', '');
+ }
+ else
+ {
+ $('.bootstrap-tagsinput input').attr('placeholder',$('#add-user').attr('data-placeholder'));
+ }
+ }
+
+ $('#add-user').on('itemRemoved', function(event) {
+ // event.item: contains the item
+ handlePlaceHolder();
+ });
+
+ $('#add-user').on('itemAdded', function(event) {
+ // event.item: contains the item
+ handlePlaceHolder();
+ });
+
+ var form = $('#group-form');
+ var validator = form.validate({
+ rules: {
+ "name": {required: true, free_text: true, rangelength: [1, 512]},
+ "slug": {required: true, free_text: true, rangelength: [1, 255]},
+ }
+ });
+
+ form.submit(function( event ) {
+ var is_valid = validator.valid();
+ if (is_valid){
+ $('body').ajax_loader();
+ validator.resetForm();
+ var group = form.serializeForm();
+ var href = $(this).attr('action');
+
+ $.ajax(
+ {
+ type: "PUT",
+ url: href,
+ data: JSON.stringify(group),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ $('body').ajax_loader('stop');
+ swal({
+ title: "Success!",
+ type: "success",
+ text: "Group info updated successfully!",
+ });
+
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ $('body').ajax_loader('stop');
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+ });
+};
\ No newline at end of file
diff --git a/assets/js/admin/edit-user.js b/assets/js/admin/edit-user.js
new file mode 100644
index 00000000..82c360a9
--- /dev/null
+++ b/assets/js/admin/edit-user.js
@@ -0,0 +1,223 @@
+$(document).ready(function() {
+ // groups
+ $('body').ajax_loader();
+
+ var options = {};
+
+ options.ui =
+ {
+ showPopover: false,
+ showErrors: true,
+ showProgressBar: true,
+ showVerdictsInsideProgressBar: true,
+ };
+
+ options.rules =
+ {
+ activated: {
+ wordTwoCharacterClasses: true,
+ wordRepetitions: true
+ }
+ };
+
+ $(':password').pwstrength(options);
+
+ var groups = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: urls.fetchGroups,
+ wildcard: '%QUERY%',
+ prepare: function (query, settings) {
+ settings.url = urls.fetchGroups+'?page=1&per_page=10&filter[]=name=@'+query+',slug=@'+query+'&filter[]=active==1&order=name,slug';
+ return settings;
+ },
+ transform: function(input){
+ var page = input.data;
+ return page;
+ }
+ }
+ });
+
+ $('#groups').tagsinput({
+ itemValue: function(item) {
+ return item.id;
+ },
+ itemText: function(item) {
+ return item.name
+ },
+ freeInput: false,
+ allowDuplicates: false,
+ typeaheadjs: [
+ {
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'groups',
+ display: function(item) {
+ return item.name;
+ },
+ templates: {
+ suggestion: function (item) {
+ return '' + item.name + '
';
+ }
+ },
+ source: groups,
+ limit: 20
+ }
+ ]
+ });
+
+ for(var group of current_groups)
+ {
+ $('#groups').tagsinput('add', group);
+ }
+
+ var form = $('#user-form');
+ var validator = form.validate({
+ rules: {
+ "first_name" : { required: true },
+ "last_name" : { required: true },
+ "email" : { required: true, custom_email: true },
+ "second_email": { custom_email: true },
+ "third_email": { custom_email: true }
+ }
+ });
+
+ var simplemdeBio = new SimpleMDE({ element: $("#bio")[0] });
+ var simplemdeStatement = new SimpleMDE({ element: $("#statement_of_interest")[0] });
+
+ $('#birthday').datepicker();
+
+ $('#country_iso_code').chosen({width: '100%', height: '34px'});
+ $("#country_iso_code").val(current_country);
+ $("#country_iso_code").trigger("chosen:updated");
+
+ $('#country_iso_code',form).change(function () {
+ validator.resetForm();
+ });
+
+ $('#language').chosen({width: '100%', height: '34px'});
+ $('#language',form).change(function () {
+ validator.resetForm();
+ });
+
+ $("#language").val(current_language);
+ $("#language").trigger("chosen:updated");
+
+ if(current_gender != '') {
+ $("#gender").val(current_gender);
+ if(current_gender == 'Specify'){
+ $('#gender_specify').removeClass('hide').fadeIn();
+ } else {
+ $('#gender_specify').fadeOut();
+ $('#gender_specify').val('');
+ }
+ }
+
+ $('#gender', form).change(function () {
+ var value = $(this).val();
+ if(value == 'Specify'){
+ $('#gender_specify').removeClass('hide').fadeIn();
+ } else {
+ $('#gender_specify').fadeOut();
+ $('#gender_specify').val('');
+ }
+ });
+
+ form.submit(function( event ) {
+ var is_valid = validator.valid();
+ if (is_valid){
+ $('body').ajax_loader();
+ validator.resetForm();
+ var user = form.serializeForm();
+
+ var birthday = user.birthday;
+ delete user.birthday;
+ if(typeof birthday != "undefined" && birthday != '') {
+ user.birthday = moment(birthday).unix();
+ }
+ else
+ {
+ user.birthday = '';
+ }
+
+ // get values
+ user.bio = simplemdeBio.value();
+ user.statement_of_interest = simplemdeStatement.value();
+
+ var groups = user.groups;
+ delete user.groups;
+ user.groups = [];
+ if(typeof groups != "undefined" && groups != '') {
+ user.groups = groups.split(",");
+ for (var i = 0; i < user.groups.length; i++) {
+ user.groups[i] = parseInt(user.groups[i], 10);
+ }
+ }
+
+ var href = $(this).attr('action');
+
+ var data = new FormData();
+
+ data.append('user', JSON.stringify(user));
+
+ if($('#pic', form)[0].files.length > 0)
+ data.append('pic', $('#pic', form)[0].files[0]);
+
+ $.ajax(
+ {
+ type: "PUT",
+ url: href,
+ data: data,
+ cache: false,
+ contentType: false,
+ processData: false,
+ timeout:60000,
+ success: function (data, textStatus, jqXHR) {
+ $('body').ajax_loader('stop');
+ swal({
+ title: "Success!",
+ type: "success",
+ text: "User info updated successfully!",
+ });
+ location.reload(true);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ $('body').ajax_loader('stop');
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ function readURL(input) {
+ if (input.files && input.files[0]) {
+ var reader = new FileReader();
+
+ reader.onload = function(e) {
+ $('#img-pic').attr('src', e.target.result);
+ }
+ reader.readAsDataURL(input.files[0]);
+ }
+ }
+
+ $("#pic", form).change(function() {
+ readURL(this);
+ });
+
+ $("#password_container").hide();
+
+ $("body").on("click", ".change-password-link", function(event){
+ $(this).hide();
+ $("#password_container").show();
+ event.preventDefault();
+ return false;
+ });
+
+ $('body').ajax_loader('stop');
+});
\ No newline at end of file
diff --git a/assets/js/admin/groups.js b/assets/js/admin/groups.js
new file mode 100644
index 00000000..85a60857
--- /dev/null
+++ b/assets/js/admin/groups.js
@@ -0,0 +1,119 @@
+// constructor
+function GroupsCrud(urls, perPage) {
+ var actions = 'Edit ' +
+ ' Delete ';
+ this.urls = urls;
+ var _this = this;
+ var templatePage = $('' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' + actions + ' ' +
+ ' ');
+
+ var directivesPage = {
+ 'tr': {
+ 'group<-context': {
+ 'td.group-identifier': 'group.id',
+ 'td.group-name': 'group.name',
+ 'td.group-slug': 'group.slug',
+ '.group-active-checkbox@value': 'group.id',
+ '.group-active-checkbox@checked': function (arg) {
+ return arg.item.active ? 'true' : '';
+ },
+ '.group-active-checkbox@id': function (arg) {
+ return 'group-active_' + arg.item.id;
+ },
+ 'a.edit-item@href': function (arg) {
+ var id = arg.item.id;
+ var href = _this.urls.edit;
+ return href.replace('@id', id);
+ },
+ 'a.delete-item@href': function (arg) {
+ var id = arg.item.id;
+ var href = _this.urls.delete;
+ return href.replace('@id', id);
+ },
+ }
+ }
+ };
+
+ // Chain constructor with call
+ BasicCrud.call(this, urls, perPage, templatePage, directivesPage, {
+ 'delete_item.title': 'Are you sure to delete this User Group?',
+ 'delete_item.text': 'This is an non reversible process!',
+ });
+}
+
+GroupsCrud.prototype = Object.create(BasicCrud.prototype);
+GroupsCrud.prototype.constructor = GroupsCrud;
+
+GroupsCrud.prototype._buildFilters = function () {
+ var term = encodeURIComponent(this.searchTerm);
+ return 'filter=name=@'+term+',slug=@'+term;
+};
+
+GroupsCrud.prototype.init = function () {
+ BasicCrud.prototype.init.call(this);
+ var _this = this;
+ // default sort
+ this.orderBy= encodeURI('+name');
+
+ var add_item_form = $('#form-add-item');
+ var add_item_dialog = $("#dialog-form-add-item");
+
+ var validator_add_item = add_item_form.validate({
+ rules: {
+ "name": {required: true, free_text: true, rangelength: [1, 512]},
+ "slug": {required: true, free_text: true, rangelength: [1, 255]},
+ }
+ });
+
+ add_item_dialog.modal({
+ show: false,
+ backdrop: "static"
+ });
+
+ add_item_dialog.on('hidden.bs.modal', function () {
+ add_item_form.cleanForm();
+ validator_add_item.resetForm();
+ $('.add-item-button').removeAttr('disabled');
+ })
+
+ $("body").on('click', ".add-item-button", function (event) {
+ add_item_dialog.modal('show');
+ $('.add-item-button').attr('disabled', 'disabled');
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',"#save-item", function(event){
+ var is_valid = add_item_form.valid();
+ if (is_valid){
+ var url = $(this).attr('href');
+ $('#save-item').attr('disabled','disabled');
+ var group = add_item_form.serializeForm();
+
+ $.ajax({
+ type: "POST",
+ url: _this.urls.add,
+ data: JSON.stringify(group),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ _this.loadPage();
+ add_item_dialog.modal('hide');
+ $('#save-item').removeAttr('disabled');
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ $('#save-item').removeAttr('disabled');
+ }
+ });
+ }
+ event.preventDefault();
+ return false;
+ });
+};
\ No newline at end of file
diff --git a/assets/js/admin/server-config.js b/assets/js/admin/server-config.js
new file mode 100644
index 00000000..d4d584f8
--- /dev/null
+++ b/assets/js/admin/server-config.js
@@ -0,0 +1,39 @@
+jQuery(document).ready(function($){
+
+ $('#server-admin','#main-menu').addClass('active');
+
+ var form_server_config = $("#form-server-configuration");
+
+ var server_config_validation = form_server_config.validate({
+ rules: {
+ "general-max-failed-login-attempts" : {required: true, number: true },
+ "general-max-failed-login-attempts-captcha" : {required: true, number: true },
+ "openid-private-association-lifetime" : {required: true, number: true },
+ "openid-session-association-lifetime" : {required: true, number: true },
+ "openid-nonce-lifetime" : {required: true, number: true },
+ "oauth2-auth-code-lifetime" : {required: true, number: true },
+ "oauth2-refresh-token-lifetime" : {required: true, number: true },
+ "oauth2-access-token-lifetime" : {required: true, number: true },
+ "oauth2-id-token-lifetime" : {required: true, number: true },
+ "oauth2-id-access-token-revoked-lifetime" : {required: true, number: true },
+ "oauth2-id-access-token-void-lifetime" : {required: true, number: true },
+ "oauth2-id-refresh-token-revoked-lifetime" : {required: true, number: true },
+ "oauth2-id-security-policy-minutes-without-exceptions" : {required: true, number: true },
+ "oauth2-id-security-policy-max-bearer-token-disclosure-attempts" : {required: true, number: true },
+ "oauth2-id-security-policy-max-invalid-client-exception-attempts" : {required: true, number: true },
+ "oauth2-id-security-policy-max-invalid-redeem-auth-code-attempts" : {required: true, number: true },
+ "oauth2-id-security-policy-max-invalid-client-credentials-attempts" : {required: true, number: true },
+ }
+ });
+
+ form_server_config.submit(function( event ) {
+ var is_valid = form_server_config.valid();
+ if (is_valid)
+ {
+ server_config_validation.resetForm();
+ return true;
+ }
+ event.preventDefault();
+ return false;
+ });
+});
diff --git a/assets/js/admin/users.js b/assets/js/admin/users.js
new file mode 100644
index 00000000..ae8005b2
--- /dev/null
+++ b/assets/js/admin/users.js
@@ -0,0 +1,240 @@
+// constructor
+function UsersCrud(urls, perPage) {
+ var actions = 'Edit ' +
+ ' Delete ';
+ this.urls = urls;
+ var _this = this;
+ var templatePage = $('' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' + actions + ' ' +
+ ' ');
+
+ var directivesPage = {
+ 'tr': {
+ 'user<-context': {
+ 'td.user-identifier': 'user.identifier',
+ 'td.user-fname': 'user.first_name',
+ 'td.user-lname': 'user.last_name',
+ 'td.user-email': function(arg){
+ return arg.item.email ? arg.item.email.replace("<","").replace(">", "") : 'TBD';
+ },
+ 'td.user-spam-type': 'user.spam_type',
+ 'td.user-last-login': function (arg) {
+ if (arg.item.last_login_date == null) return 'N/A';
+ return moment.unix(arg.item.last_login_date).format();
+ },
+ '.user-active-checkbox@value': 'user.id',
+ '.user-active-checkbox@checked': function (arg) {
+ return arg.item.active ? 'true' : '';
+ },
+ '.user-active-checkbox@id': function (arg) {
+ var user_id = arg.item.id;
+ return 'user-active_' + user_id;
+ },
+ 'a.edit-item@href': function (arg) {
+ var id = arg.item.id;
+ var href = _this.urls.edit;
+ return href.replace('@id', id);
+ },
+ 'a.delete-item@href': function (arg) {
+ var id = arg.item.id;
+ var href = _this.urls.delete;
+ return href.replace('@id', id);
+ },
+ }
+ }
+ };
+
+ // Chain constructor with call
+ BasicCrud.call(this, urls, perPage, templatePage, directivesPage, {
+ 'delete_item.title': 'Are you sure to delete this registered User?',
+ 'delete_item.text': 'This is an non reversible process!',
+ });
+}
+
+UsersCrud.prototype = Object.create(BasicCrud.prototype);
+UsersCrud.prototype.constructor = UsersCrud;
+
+UsersCrud.prototype._buildFilters = function () {
+ var term = encodeURIComponent(this.searchTerm.trim());
+ return 'filter=first_name=@'+term+',last_name=@'+term+',email=@'+term+',full_name=@'+term;
+};
+
+UsersCrud.prototype.init = function () {
+ // Chain parent method
+ BasicCrud.prototype.init.call(this);
+ var _this = this;
+ // default sort
+ this.orderBy= encodeURI('+email');
+
+ $("body").on('click', ".user-active-checkbox", function (event) {
+ var active = $(this).is(':checked');
+ var userId = $(this).attr('value');
+ var url = active ? _this.urls.unlock : _this.urls.lock;
+ url = url.replace('@id', userId);
+ var verb = active ? 'PUT' : 'DELETE';
+
+ $.ajax(
+ {
+ type: verb,
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ success: function (data, textStatus, jqXHR) {
+ _this.loadPage();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ var options = {};
+
+ options.ui =
+ {
+ showPopover: false,
+ showErrors: true,
+ showProgressBar: true,
+ showVerdictsInsideProgressBar: true,
+ };
+
+ options.rules =
+ {
+ activated: {
+ wordTwoCharacterClasses: true,
+ wordRepetitions: true
+ }
+ };
+
+ $(':password').pwstrength(options);
+
+ var groups = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: _this.urls.fetchGroups,
+ wildcard: '%QUERY%',
+ prepare: function (query, settings) {
+ settings.url = _this.urls.fetchGroups+'?filter[]=name=@'+query+'&filter[]=active==1';
+ return settings;
+ },
+ transform: function(input){
+ var page = input.data;
+ return page;
+ }
+ }
+ });
+
+ $('#groups').tagsinput({
+ itemValue: function(item) {
+ return item.id;
+ },
+ itemText: function(item) {
+ return item.name
+ },
+ freeInput: false,
+ allowDuplicates: false,
+ typeaheadjs: [
+ {
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'groups',
+ display: function(item) {
+ return item.name;
+ },
+ templates: {
+ suggestion: function (item) {
+ return '' + item.name + '
';
+ }
+ },
+ source: groups,
+ limit: 10
+ }
+ ]
+ });
+
+ var add_item_form = $('#form-add-item');
+ var add_item_dialog = $("#dialog-form-add-item");
+
+ var validator_add_item = add_item_form.validate({
+ rules: {
+ "first_name": {required: true, free_text: true, rangelength: [1, 255]},
+ "last_name": {required: true, free_text: true, rangelength: [1, 512]},
+ "email": {required: true, custom_email: true},
+ password: {
+ required: true,
+ minlength: 8
+ },
+ "password_confirmation": {
+ minlength: 8,
+ equalTo: "#password"
+ },
+ }
+ });
+
+ add_item_dialog.modal({
+ show: false,
+ backdrop: "static"
+ });
+
+ add_item_dialog.on('hidden.bs.modal', function () {
+ add_item_form.cleanForm();
+ validator_add_item.resetForm();
+ $('.add-item-button').removeAttr('disabled');
+ })
+
+ $("body").on('click', ".add-item-button", function (event) {
+ add_item_dialog.modal('show');
+ $('.add-item-button').attr('disabled', 'disabled');
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',"#save-item", function(event){
+ var is_valid = add_item_form.valid();
+ if (is_valid){
+ var url = $(this).attr('href');
+ $('#save-item').attr('disabled','disabled');
+ var user = add_item_form.serializeForm();
+ var groups = user.groups;
+ delete user.groups;
+
+ if(typeof groups != "undefined" && groups != '') {
+ user.groups = groups.split(",");
+ for (var i = 0; i < user.groups.length; i++) {
+ user.groups[i] = parseInt(user.groups[i], 10);
+ }
+ }
+
+ $.ajax({
+ type: "POST",
+ url: _this.urls.add,
+ data: JSON.stringify(user),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ _this.loadPage();
+ add_item_dialog.modal('hide');
+ $('#save-item').removeAttr('disabled');
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ $('#save-item').removeAttr('disabled');
+ }
+ });
+ }
+ event.preventDefault();
+ return false;
+ });
+
+};
diff --git a/assets/js/ajax.utils.js b/assets/js/ajax.utils.js
new file mode 100644
index 00000000..70bc53f3
--- /dev/null
+++ b/assets/js/ajax.utils.js
@@ -0,0 +1,76 @@
+function ajaxError(jqXHR, textStatus, errorThrown){
+ var HTTP_status = jqXHR.status;
+ if(HTTP_status != 200){
+ response = $.parseJSON(jqXHR.responseText);
+ if(HTTP_status == 412 ){
+ var msg = '';
+
+ for (const [key, value] of Object.entries(response.errors)) {
+ msg += isNaN(key) ? `* ${key}: ` : "";
+ msg += `${value}\n`;
+ }
+
+ displayErrorMessage('Validation error', msg);
+ return;
+ }
+ if(HTTP_status == 404 ){
+ var msg = '';
+ for(var property in response.messages) {
+ msg +='* '+response.messages[property]+'\n';
+ }
+ displayErrorMessage('Entity Not Found', msg);
+ return;
+ }
+ displayErrorMessage('You got an error!',response.error);
+ return;
+ }
+ displayErrorMessage('You got an error!','server error');
+}
+
+function displayErrorMessage(title, message){
+ $('#modal_error').remove();
+ var modal = $('' +
+ '
' +
+ '
' +
+ ''+
+ '
'+
+ ''+
+ '
'+
+ '
'+
+ '
');
+ $('body').append(modal);
+ modal.modal({show:true});
+
+ modal.on('hidden.bs.modal', function (e) {
+ $('.modal-backdrop').remove();
+ })
+}
+
+function displaySuccessMessage(message, $element){
+ var element_id = $element.attr('id');
+ var $alert = $('.alert-success').filter(function() {
+ return $(this).attr("for") === element_id;
+ });
+ if($alert.length){
+ $('.msg',$alert).html(message || "");
+ }
+ else{
+ var $alert = $('×
').attr('for',element_id);
+ $('.msg',$alert).html(message || "");
+ $alert.insertBefore($element);
+ }
+}
+
+function displayAlert(msg,after){
+ $('.alert-danger').remove();
+ var alert = $('× '+msg+'
');
+ alert.insertAfter(after);
+}
\ No newline at end of file
diff --git a/assets/js/auth/email-verification.js b/assets/js/auth/email-verification.js
new file mode 100644
index 00000000..1875057c
--- /dev/null
+++ b/assets/js/auth/email-verification.js
@@ -0,0 +1,38 @@
+(function( $ ){
+
+ $('body').ajax_loader();
+
+ $(document).ready(function($){
+
+ var form = $('#form-verification');
+
+ var validator = form.validate({
+ rules: {
+
+ email: {
+ required: true
+ },
+ 'g_recaptcha_hidden': {required: true}
+ },
+ messages: {
+ 'g_recaptcha_hidden': { required: 'Please confirm that you are not a robot.'}
+ }
+ });
+
+ form.submit(function(e){
+ var is_valid = $(this).valid();
+ if (!is_valid) {
+ e.preventDefault();
+ return false;
+ }
+ $('.btn-primary').attr('disabled', 'disabled');
+ return true;
+ });
+
+ $('body').ajax_loader('stop');
+ });
+
+
+
+// End of closure.
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/auth/registration.js b/assets/js/auth/registration.js
new file mode 100644
index 00000000..2c499b6a
--- /dev/null
+++ b/assets/js/auth/registration.js
@@ -0,0 +1,76 @@
+(function( $ ){
+ $('body').ajax_loader();
+
+ $(document).ready(function($){
+
+ var options = {};
+
+ options.ui =
+ {
+ showPopover: false,
+ showErrors: true,
+ showProgressBar: true,
+ showVerdictsInsideProgressBar: true,
+ };
+
+ options.rules =
+ {
+ activated: {
+ wordTwoCharacterClasses: true,
+ wordRepetitions: true
+ }
+ };
+
+ $(':password').pwstrength(options);
+
+ $('#country_iso_code').chosen({width: '100%', height: '34px'});
+
+ var form = $('#form-registration');
+
+ var validator = form.validate({
+ rules: {
+ first_name: {
+ required: true,
+ },
+ last_name: {
+ required: true,
+ },
+ "country_iso_code": {
+ required: true,
+ },
+ password: {
+ required: true,
+ minlength: 8
+ },
+ email: {
+ required: true
+ },
+ "password_confirmation" : {
+ minlength: 8,
+ equalTo : "#password"
+ },
+ 'agree_code_of_conduct': {required: true},
+ 'g_recaptcha_hidden': {required: true}
+ },
+ messages: {
+ 'g_recaptcha_hidden': { required: 'Please confirm that you are not a robot.'}
+ }
+ });
+
+ form.submit(function(e){
+ var is_valid = $(this).valid();
+ if (!is_valid) {
+ e.preventDefault();
+ return false;
+ }
+ $('.btn-primary').attr('disabled', 'disabled');
+ return true;
+ });
+
+ $('body').ajax_loader('stop');
+ });
+
+
+
+// End of closure.
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/auth/registration_success.js b/assets/js/auth/registration_success.js
new file mode 100644
index 00000000..61dc0a40
--- /dev/null
+++ b/assets/js/auth/registration_success.js
@@ -0,0 +1,16 @@
+(function( $ ){
+ $('body').ajax_loader();
+
+ $(document).ready(function($){
+
+ var redirect = $('#redirect_url');
+ if(redirect.length > 0){
+ var href = $(redirect).attr('href');
+ setTimeout(function(){ window.location = href; }, 10000);
+ }
+
+ $('body').ajax_loader('stop');
+ });
+
+// End of closure.
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/auth/reset_password.js b/assets/js/auth/reset_password.js
new file mode 100644
index 00000000..59e19f9b
--- /dev/null
+++ b/assets/js/auth/reset_password.js
@@ -0,0 +1,62 @@
+(function( $ ){
+ $('body').ajax_loader();
+
+ $(document).ready(function($){
+
+
+ var options = {};
+
+ options.ui =
+ {
+ showPopover: false,
+ showErrors: true,
+ showProgressBar: true,
+ showVerdictsInsideProgressBar: true,
+ };
+
+ options.rules =
+ {
+ activated: {
+ wordTwoCharacterClasses: true,
+ wordRepetitions: true
+ }
+ };
+
+ $(':password').pwstrength(options);
+
+ var form = $('#form-password-reset');
+
+ var validator = form.validate({
+ rules: {
+ password: {
+ required: true,
+ minlength: 8
+ },
+ "password_confirmation" : {
+ minlength: 8,
+ equalTo : "#password"
+ },
+ 'g_recaptcha_hidden': {required: true}
+ },
+ messages: {
+ 'g_recaptcha_hidden': { required: 'Please confirm that you are not a robot.'}
+ }
+ });
+
+ form.submit(function(e){
+ var is_valid = $(this).valid();
+ if (!is_valid) {
+ e.preventDefault();
+ return false;
+ }
+ $('.btn-primary').attr('disabled', 'disabled');
+ return true;
+ });
+
+ $('body').ajax_loader('stop');
+ });
+
+
+
+// End of closure.
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/auth/send_password_link.js b/assets/js/auth/send_password_link.js
new file mode 100644
index 00000000..8190f9c3
--- /dev/null
+++ b/assets/js/auth/send_password_link.js
@@ -0,0 +1,46 @@
+(function( $ ){
+
+ $('body').ajax_loader();
+
+ $(document).ready(function($){
+
+ var form = $('#form-send-password-reset-link');
+
+ var validator = form.validate({
+ rules: {
+ email: {
+ required: true
+ },
+ 'g_recaptcha_hidden': {required: true}
+ },
+ messages: {
+ 'g_recaptcha_hidden': { required: 'Please confirm that you are not a robot.'}
+ }
+ });
+
+ form.submit(function(e){
+ var is_valid = $(this).valid();
+ if (!is_valid) {
+ e.preventDefault();
+ return false;
+ }
+ $('.btn-primary').attr('disabled', 'disabled');
+ return true;
+ });
+
+ $(document).ready(function($){
+
+ var redirect = $('#redirect_url');
+ if(redirect.length > 0){
+ var href = $(redirect).attr('href');
+ setTimeout(function(){ window.location = href; }, 3000);
+ }
+
+ });
+
+ $('body').ajax_loader('stop');
+
+ });
+
+// End of closure.
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/auth/set_password.js b/assets/js/auth/set_password.js
new file mode 100644
index 00000000..e9012a61
--- /dev/null
+++ b/assets/js/auth/set_password.js
@@ -0,0 +1,74 @@
+(function( $ ){
+
+ $('body').ajax_loader();
+
+ $(document).ready(function($){
+
+ var options = {};
+
+ options.ui =
+ {
+ showPopover: false,
+ showErrors: true,
+ showProgressBar: true,
+ showVerdictsInsideProgressBar: true,
+ };
+
+ options.rules =
+ {
+ activated: {
+ wordTwoCharacterClasses: true,
+ wordRepetitions: true
+ }
+ };
+
+ $(':password').pwstrength(options);
+ $('#country_iso_code').chosen({width: '100%', height: '34px'});
+
+ var form = $('#form-password-set');
+
+ var validator = form.validate({
+ rules: {
+ first_name: {
+ required: true,
+ },
+ last_name: {
+ required: true,
+ },
+ company: {
+ required: true,
+ },
+ "country_iso_code": {
+ required: true,
+ },
+ password: {
+ required: true,
+ minlength: 8
+ },
+ "password_confirmation" : {
+ minlength: 8,
+ equalTo : "#password"
+ },
+ 'g_recaptcha_hidden': {required: true}
+ },
+ messages: {
+ 'g_recaptcha_hidden': { required: 'Please confirm that you are not a robot.'}
+ }
+ });
+
+ form.submit(function(e){
+ var is_valid = $(this).valid();
+ if (!is_valid) {
+ e.preventDefault();
+ return false;
+ }
+ $('.btn-primary').attr('disabled', 'disabled');
+ return true;
+ });
+
+ $('body').ajax_loader('stop');
+
+ });
+
+// End of closure.
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/auth/set_password_success.js b/assets/js/auth/set_password_success.js
new file mode 100644
index 00000000..b14556d9
--- /dev/null
+++ b/assets/js/auth/set_password_success.js
@@ -0,0 +1,24 @@
+(function( $ ){
+
+ $('body').ajax_loader();
+
+ $(document).ready(function($){
+
+ var redirect = $('#redirect_url');
+
+ if(redirect.length > 0){
+ var href = $(redirect).attr('href');
+ if(email != '')
+ href = href +'#login=1&email=' + email;
+
+ setTimeout(function(){ window.location = href; }, 3000);
+ return;
+ }
+
+ setTimeout(function(){ window.location = '/accounts/user/profile'; }, 3000);
+
+ $('body').ajax_loader('stop');
+ });
+
+// End of closure.
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/basic-crud.js b/assets/js/basic-crud.js
new file mode 100644
index 00000000..b73a3965
--- /dev/null
+++ b/assets/js/basic-crud.js
@@ -0,0 +1,249 @@
+jQuery(document).ready(function ($) {
+});
+
+// constructor
+function BasicCrud(urls, perPage, templatePage, directivesPage, messages) {
+ // settings
+ this.urls = urls;
+ this.perPage = perPage;
+ this.templatePage = templatePage;
+ this.directivesPage = directivesPage;
+ this.maxItemsPerPage = 10;
+ this.messages = messages;
+ // state
+ this.currentPage = null;
+ this.searchTerm = null;
+ this.orderBy = null;
+ this.orderByDir = null;
+}
+
+// methods
+BasicCrud.prototype = {
+ init: function () {
+ var _this = this;
+ jQuery(document).ready(function ($) {
+
+ // set current menu item active
+ $('#server-admin', '#main-menu').addClass('active');
+
+ $('#btn-do-search-clear').hide();
+
+ $("body").on('click',".sort-header",function (event) {
+ $(".sort-header").removeClass("current");
+ $(this).addClass('current');
+ var dir = '+';
+ if($(this).hasClass('asc')){
+ $(this).removeClass('asc');
+ $(this).addClass('desc');
+ dir = '-';
+ }
+ else {
+ dir = '+';
+ $(this).removeClass('desc');
+ $(this).addClass('asc');
+ }
+
+ var field = $(this).data('field');
+ _this.orderBy = encodeURI(dir+field);
+ _this.loadPage();
+ });
+
+ var currentTermFromDeepLink = $(window).url_fragment('getParam', 'term');
+ if (currentTermFromDeepLink != null) {
+ $('#search-term').val(currentTermFromDeepLink);
+ $('#btn-do-search-clear').show();
+ }
+
+ _this.loadPage();
+
+ $("body").on('click', ".page-link", function (event) {
+ _this.currentPage = $(this).attr('data-page');
+ _this.loadPage();
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('keydown', "#search-term", function (event) {
+ if (event.keyCode === 13) {
+ $('#btn-do-search').trigger('click');
+ event.preventDefault();
+ return false;
+ }
+ return true;
+ });
+
+ $("body").on('click', "#btn-do-search", function (event) {
+ _this.currentPage = 1;
+ _this.searchTerm = $('#search-term').val()
+ $('#btn-do-search-clear').show();
+ _this.loadPage();
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click', "#btn-do-search-clear", function (event) {
+ $('#search-term').val('');
+ _this.searchTerm = '';
+ $(this).hide();
+ _this.loadPage();
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click', 'a.delete-item', function (event) {
+
+ var url = $(this).attr('href');
+
+ swal({
+ title: _this.messages['delete_item.title'],
+ text: _this.messages['delete_item.text'],
+ type: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: "Yes, Delete it!",
+ }).then(
+ function (result) {
+ if (result.value) {
+ $.ajax(
+ {
+ type: "DELETE",
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ _this.loadPage();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ });
+ event.preventDefault();
+ return false;
+ });
+ });
+ },
+ _buildFilters: function () {
+
+ },
+ loadPage: function () {
+ var _this = this;
+ $('body').ajax_loader();
+ var currentPageFromDeepLink = $(window).url_fragment('getParam', 'page');
+ var currentTermFromDeepLink = $(window).url_fragment('getParam', 'term');
+
+ if (this.currentPage == null && currentPageFromDeepLink != null) {
+ this.currentPage = currentPageFromDeepLink;
+ }
+
+ if (this.searchTerm == null && currentTermFromDeepLink != null) {
+ this.searchTerm = currentTermFromDeepLink;
+ }
+
+ if (this.currentPage == null)
+ this.currentPage = 1;
+
+ if (this.searchTerm != null)
+ $(window).url_fragment('setParam', 'term', this.searchTerm);
+
+ $(window).url_fragment('setParam', 'page', this.currentPage);
+
+ window.location.hash = $(window).url_fragment('serialize');
+
+ var url = this.urls.load + '?page=' + parseInt(this.currentPage) + '&per_page=' + this.perPage;
+
+ if (this.searchTerm != null && this.searchTerm != '') {
+ url += '&' + this._buildFilters();
+ }
+
+ if (this.orderBy != null && this.orderBy != '') {
+ url += '&order=' + this.orderBy;
+ }
+
+ $.ajax({
+ type: "GET",
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (page, textStatus, jqXHR) {
+ //load data...
+ var items = page.data;
+ if (items.length == 0) {
+ $('.label-info').show();
+ $('#table').hide();
+ $('#search-container').hide();
+ $('#pager-container').hide();
+ $('body').ajax_loader('stop');
+ return;
+ }
+
+ $('.label-info').hide();
+ $('#table').show();
+ $('#search-container').show();
+ $('#pager-container').show();
+
+ var body = _this.templatePage.render(items, _this.directivesPage);
+ $('#body-table', '#table').remove();
+ $('#table').append('' + body.html() + ' ');
+
+ var templatePager = $('');
+
+ var maxPages2Show = 20;
+ var totalPages = parseInt(Math.ceil(page.total / _this.perPage));
+ if (maxPages2Show > totalPages) maxPages2Show = totalPages;
+ var currentPage = page.current_page;
+ var currentRelativePage = currentPage % maxPages2Show;
+ if (currentRelativePage == 0) currentRelativePage = maxPages2Show;
+ var pages = [];
+
+ for (var i = 1; i <= maxPages2Show; i++) {
+ pages.push({
+ nbr: i + (currentPage - currentRelativePage)
+ });
+ }
+
+ var directivesPager = {
+ 'li': {
+ 'i<-context': {
+ '@class': function (arg) {
+ return arg.item.nbr == page.current_page ? "active" : "";
+ },
+ 'a': function (arg) {
+ return arg.item.nbr;
+ },
+ 'a@class': function (arg) {
+ return "page-link"
+ },
+ 'a@data-page': function (arg) {
+ return arg.item.nbr;
+ },
+ }
+ }
+ };
+ var pager = templatePager.render(pages, directivesPager);
+
+ $('#pager', '#pager-container').remove();
+ var prev_item = '';
+ var next_item = '';
+ if (pages.length > 0) {
+ prev_item = (pages[0].nbr > 1) ? '« ' : '';
+ next_item = (pages[pages.length - 1].nbr < totalPages) ? '» ' : '';
+ }
+ $('#pager-container').append('');
+
+ $('body').ajax_loader('stop');
+
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ $('body').ajax_loader('stop');
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+ }
+};
diff --git a/assets/js/jquery-ajax-loader.js b/assets/js/jquery-ajax-loader.js
new file mode 100644
index 00000000..8cb899ed
--- /dev/null
+++ b/assets/js/jquery-ajax-loader.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright 2014 Openstack Foundation
+ * 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.
+ **/
+(function( $ ){
+
+ var element = null;
+ var text = 'loading ...';
+ var methods = {
+ init : function(options) {
+ element = $(this);
+
+ if(element.find('#resultLoading').attr('id') != 'resultLoading'){
+ element.append('');
+ }
+
+ jQuery('#resultLoading').css({
+ 'width':'100%',
+ 'height':'100%',
+ 'position':'fixed',
+ 'z-index':'10000000',
+ 'top':'0',
+ 'left':'0',
+ 'margin':'auto'
+ });
+
+ jQuery('#resultLoading .bg').css({
+ 'background':'#000000',
+ 'opacity':'0.7',
+ 'width':'100%',
+ 'height':'100%',
+ 'position':'absolute',
+ 'top':'0'
+ });
+
+ jQuery('#resultLoading>div:first').css({
+ 'width': '250px',
+ 'height':'75px',
+ 'text-align': 'center',
+ 'position': 'fixed',
+ 'top':'0',
+ 'left':'0',
+ 'right':'0',
+ 'bottom':'0',
+ 'margin':'auto',
+ 'font-size':'16px',
+ 'z-index':'10',
+ 'color':'#ffffff'
+
+ });
+
+ jQuery('#resultLoading .bg').height('100%');
+ jQuery('#resultLoading').fadeIn(500);
+ element.css('cursor', 'wait');
+ },
+ stop: function(){
+ $('#resultLoading .bg').height('100%');
+ $('#resultLoading').fadeOut(1000);
+ element.css('cursor', 'default');
+ }
+ };
+
+ $.fn.ajax_loader = function(methodOrOptions) {
+ if ( methods[methodOrOptions] ) {
+ return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+ } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
+ // Default to "init"
+ return methods.init.apply( this, arguments );
+ } else {
+ $.error( 'Method ' + method + ' does not exist on jQuery.ajax_loader' );
+ }
+ };
+
+ //helper functions
+
+// End of closure.
+}( jQuery));
\ No newline at end of file
diff --git a/assets/js/jquery.cleanform.js b/assets/js/jquery.cleanform.js
new file mode 100644
index 00000000..af97e796
--- /dev/null
+++ b/assets/js/jquery.cleanform.js
@@ -0,0 +1,10 @@
+(function( $ ){
+ $.fn.cleanForm = function() {
+ //after serialize clear all!
+ $('input',this).val('');
+ $('select',this).val('');
+ $('textarea',this).val('');
+ this.find(':checkbox').removeAttr('checked');
+ return this;
+ };
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/jquery.serialize.js b/assets/js/jquery.serialize.js
new file mode 100644
index 00000000..a6b91cec
--- /dev/null
+++ b/assets/js/jquery.serialize.js
@@ -0,0 +1,32 @@
+(function( $ ){
+ $.fn.serializeForm = function() {
+ var o = {};
+ var a = this.serializeArray();
+ var $form = $(this);
+
+ $.each(a, function() {
+ //special case for checkboxes
+ var $element = $('#'+this.name,$form);
+ if($element.is(':checkbox')){
+ this.value = $element.is(':checked');
+ }
+ if (o[this.name]) {
+ if (!o[this.name].push) {
+ o[this.name] = [o[this.name]];
+ }
+ o[this.name].push(this.value || '');
+ } else {
+ o[this.name] = this.value;
+ }
+ });
+
+ //add missing checkboxes (non checked ones)
+ $('input:checkbox', $form).each(function() {
+ var id = $(this).attr('id');
+ var value = $(this).is(':checked');
+ if (!o.hasOwnProperty(id))
+ o[id] = value;
+ });
+ return o;
+ };
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/jquery.validate.additional.custom.methods.js b/assets/js/jquery.validate.additional.custom.methods.js
new file mode 100644
index 00000000..0acd3876
--- /dev/null
+++ b/assets/js/jquery.validate.additional.custom.methods.js
@@ -0,0 +1,115 @@
+
+var regex_ipv4 = /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i;
+
+var regex_ipv6 = /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i;
+
+jQuery.validator.addMethod("ipV4", function(value, element) {
+ return this.optional(element) || regex_ipv4.test(value);
+}, "Please enter a valid IPV4 address");
+
+jQuery.validator.addMethod("ipv4V6Set", function(value, element) {
+ var ips = value.split(',');
+ for(var ip of ips)
+ {
+ var valid = regex_ipv4.test(ip);
+ if(!valid)
+ valid = regex_ipv6.test(ip);
+ if(!valid) return false;
+ }
+ return true
+}, "Please enter a valid IP (V4/V6) address");
+
+jQuery.validator.addMethod("ssl_uri", function(value, element) {
+ var regex = /^https:\/\/([\w@][\w.:@]+)\/?[\w\.?=%&=\-@/$,]*$/ig;
+ return this.optional(element) || regex.test(value);
+}, "Uri must under https schema.");
+
+
+jQuery.validator.addMethod("scopename", function(value, element) {
+ var regex = /^[a-zA-Z0-9\-\.\,\:\_\/]+$/ig;
+ return this.optional(element) || regex.test(value);
+}, "Please enter a valid scope name.");
+
+jQuery.validator.addMethod("free_text", function(value, element) {
+ return this.optional(element) || /^[a-zA-Z0-9\-_.,()'"\s\:\/]+$/i.test(value);
+}, "Letters or punctuation only please");
+
+jQuery.validator.addMethod("endpointroute", function(value, element) {
+ return this.optional(element) || /^\/([\w@][\w.:@]+)\/?[\w\.?=%&=\-@/$,{}]*$/ig.test(value);
+}, "Please enter a valid endpoint route");
+
+jQuery.validator.addMethod("pem_public_key", function(value, element, options) {
+ if(!options) return true;
+ var res1 = value.indexOf('-----BEGIN PUBLIC KEY-----');
+ var res2 = value.indexOf('-----BEGIN RSA PUBLIC KEY-----');
+ var res3 = value.indexOf('-----END PUBLIC KEY-----');
+ var res4 = value.indexOf('-----END RSA PUBLIC KEY-----');
+ var pck1 = (res1 != -1 && res3 != -1);
+ var pck8 = (res2 != -1 && res4 != -1);
+ return pck1 || pck8;
+}, "Please enter a valid PEM Public Key format(PCK#8/PCK#1)");
+
+jQuery.validator.addMethod("pem_private_key", function(value, element, options) {
+ if(!options) return true;
+ var res1 = value.indexOf('-----BEGIN PRIVATE KEY-----');
+ var res2 = value.indexOf('-----BEGIN RSA PRIVATE KEY-----');
+ var res3 = value.indexOf('-----END PRIVATE KEY-----');
+ var res4 = value.indexOf('-----END RSA PRIVATE KEY-----');
+ var pck1 = (res1 != -1 && res3 != -1);
+ var pck8 = (res2 != -1 && res4 != -1);
+ return pck1 || pck8;
+}, "Please enter a valid PEM Private Key format(PCK#8/PCK#1)");
+
+jQuery.validator.addMethod("private_key_password_required", function(value, element, options) {
+ var private_key_pem = $(options.pem_content_id).val();
+ return !(private_key_pem.indexOf('ENCRYPTED') != -1 && value == '');
+}, "Please enter a password for Private Key (PCK#8/PCK#1)");
+
+$.validator.addMethod("dateUS", function (value, element,options) {
+ //mm/dd/yyyy
+ var check = false,
+ regex = /^\d{1,2}\/\d{1,2}\/\d{4}$/,
+ adata, gg, mm, aaaa, xdata;
+ if ( regex.test(value)) {
+ adata = value.split("/");
+ mm = parseInt(adata[0], 10);
+ gg = parseInt(adata[1], 10);
+ aaaa = parseInt(adata[2], 10);
+ xdata = new Date(Date.UTC(aaaa, mm - 1, gg, 12, 0, 0, 0));
+ if ( ( xdata.getUTCFullYear() === aaaa ) && ( xdata.getUTCMonth () === mm - 1 ) && ( xdata.getUTCDate() === gg ) ) {
+ check = true;
+ } else {
+ check = false;
+ }
+ } else {
+ check = false;
+ }
+ return this.optional(element) || check;
+}, "Please enter a valid date.");
+
+$.validator.addMethod("custom_email", function(value, element) {
+ if(!value) return true;
+ value = value.trim();
+ if(value == '') return true;
+ return /^\S+@\S+(\.\S+)*$/.test( value )
+}, "field should be a valid email.");
+
+// override jquery validate plugin defaults
+$.validator.setDefaults({
+ ignore: [],
+ highlight: function(element) {
+ $(element).closest('.form-group').addClass('has-error');
+ },
+ unhighlight: function(element) {
+ $(element).closest('.form-group').removeClass('has-error');
+ },
+ errorElement: 'span',
+ errorClass: 'help-block',
+ errorPlacement: function(error, element) {
+ if(element.parent('.input-group').length) {
+ error.insertAfter(element.parent());
+ } else {
+ error.insertAfter(element);
+ }
+ }
+});
\ No newline at end of file
diff --git a/assets/js/login.js b/assets/js/login.js
new file mode 100644
index 00000000..0e005a91
--- /dev/null
+++ b/assets/js/login.js
@@ -0,0 +1,41 @@
+jQuery(document).ready(function($){
+
+ $('#login_form').submit(function(event){
+ if(!navigator.cookieEnabled){
+ event.preventDefault();
+ checkCookiesEnabled();
+ return false;
+ }
+ $('.btn-primary').attr('disabled', 'disabled');
+ return true;
+ });
+
+ $(".toggle-password").click(function() {
+
+ $(this).toggleClass("fa-eye-slash");
+ var currentTitle = $(this).attr("title");
+ if(currentTitle == "Show Password")
+ currentTitle = "Hide Password";
+ else
+ currentTitle = "Show Password";
+ $(this).attr("title", currentTitle);
+ var input = $($(this).attr("toggle"));
+ if (input.attr("type") == "password") {
+ input.attr("type", "text");
+ } else {
+ input.attr("type", "password");
+ }
+ });
+
+ checkCookiesEnabled();
+});
+
+function checkCookiesEnabled(){
+ var cookieEnabled = navigator.cookieEnabled;
+
+ return cookieEnabled || showCookieFail();
+}
+
+function showCookieFail(){
+ $('#cookies-disabled-dialog').show();
+}
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/admin/api-scope-groups.js b/assets/js/oauth2/profile/admin/api-scope-groups.js
new file mode 100644
index 00000000..d310e076
--- /dev/null
+++ b/assets/js/oauth2/profile/admin/api-scope-groups.js
@@ -0,0 +1,265 @@
+function loadGroups(){
+ var link = ApiScopeGroupUrls.get;
+ $.ajax(
+ {
+ type: "GET",
+ url: link,
+ dataType: "json",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ //load data...
+ var groups = page.data;
+
+ if(groups.length > 0) {
+ var template = $(' Edit Delete ');
+ var directives = {
+ 'tr': {
+ 'group<-context': {
+ 'td.name': 'group.name',
+ '.api-scope-group-active-checkbox@value': 'group.id',
+ '.api-scope-group-active-checkbox@checked': function (arg) {
+ return arg.item.active ? 'true' : '';
+ },
+ '.api-scope-group-active-checkbox@data-group-id': 'group.id',
+ '.api-scope-group-active-checkbox@id': function (arg) {
+ var id = arg.item.id;
+ return 'api-scope-group-active_' + id;
+ },
+ 'a.edit-api-scope-group@href': function (arg) {
+ var id = arg.item.id;
+ var href = ApiScopeGroupUrls.edit;
+ return href.replace('-1', id);
+ },
+ 'a.delete-api-scope-group@href': function (arg) {
+ var id = arg.item.id;
+ var href = ApiScopeGroupUrls.delete;
+ return href.replace('-1', id);
+ }
+ }
+ }
+ };
+ var html = template.render(groups, directives);
+ $('#body-api-scope-groups').html(html.html());
+ $('#info-api-scope-groups').hide()
+ }
+ else
+ {
+ $('#info-api-scope-groups').show();
+ $('#body-api-scope-groups').hide();
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+}
+
+$(document).ready(function() {
+
+ $('#server-admin', '#main-menu').addClass('active');
+
+ //validation rules on new server form
+ var scope_group_form = $('#form-api-scope-group');
+ var dialog_scope_group = $('#dialog-form-api-scope-group');
+
+ var scope_group_validator = scope_group_form.validate({
+ rules: {
+ "name" : { required: true, free_text:true,rangelength: [1, 255]},
+ "users" : { required: true },
+ "scopes" : { required: true },
+ }
+ });
+
+ dialog_scope_group.modal({
+ show:false,
+ backdrop:"static"
+ });
+
+ dialog_scope_group.on('hidden.bs.modal', function (e) {
+ scope_group_form.cleanForm();
+ scope_group_validator.resetForm();
+ $('#users').tagsinput('removeAll');
+ $('#scopes').tagsinput('removeAll');
+ })
+
+ $("body").on('click',".add-api-scope-group",function(event){
+ dialog_scope_group.modal('show');
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".refresh-groups",function(event){
+ loadGroups()
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".api-scope-group-active-checkbox",function(event){
+ var active = $(this).is(':checked');
+ var group_id = $(this).attr('data-group-id');
+ var url = active? ApiScopeGroupUrls.activate : ApiScopeGroupUrls.deactivate;
+ url = url.replace('@id',group_id);
+ var verb = active ? 'PUT':'DELETE';
+ $.ajax(
+ {
+ type: verb,
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ $("body").on('click',".save-api-scope-group",function(event){
+ var is_valid = scope_group_form.valid();
+ if (is_valid){
+ var api_scope_group = scope_group_form.serializeForm();
+ $.ajax({
+ type: "POST",
+ url: ApiScopeGroupUrls.add,
+ data: JSON.stringify(api_scope_group),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadGroups();
+ dialog_scope_group.modal('hide');
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".delete-api-scope-group",function(event){
+ if(confirm("Are you sure?")){
+ var href = $(this).attr('href');
+ $.ajax(
+ {
+ type: "DELETE",
+ url: href,
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadGroups();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ // modal controls
+
+ // scopes
+
+ var scopes = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: ApiScopeGroupUrls.fetchScopes,
+ wildcard: '%QUERY%',
+ prepare: function (query, settings) {
+ settings.url = ApiScopeGroupUrls.fetchScopes+'?filter[]=name=@'+query+'&filter[]=is_assigned_by_groups==1';
+ return settings;
+ },
+ transform: function(input){
+ var page = input.data;
+ return page;
+ }
+ }
+ });
+
+ $('#scopes').tagsinput({
+ itemValue: function(item) {
+ return item.id;
+ },
+ itemText: function(item) {
+ return item.name
+ },
+ freeInput: false,
+ allowDuplicates: false,
+ typeaheadjs: [
+ {
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'scopes',
+ display: function(item) {
+ return item.name;
+ },
+ templates: {
+ suggestion: function (item) {
+ return '' + item.name + '
';
+ }
+ },
+ source: scopes,
+ limit: 10
+ }
+ ]
+ });
+
+ // users
+
+ var users = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: ApiScopeGroupUrls.fetchUsers,
+ wildcard: '%QUERY%',
+ prepare: function (query, settings) {
+ settings.url = ApiScopeGroupUrls.fetchUsers+'?filter=first_name=@'+query+',last_name=@'+query+',email=@'+query;
+ return settings;
+ },
+ transform: function(input){
+ var page = input.data;
+ return page;
+ }
+ }
+ });
+
+ $('#users').tagsinput({
+ itemValue: function(item) {
+ return item.id;
+ },
+ itemText: function(item) {
+ return item.first_name + ' ' + item.last_name;
+ },
+ freeInput: false,
+ allowDuplicates: false,
+ typeaheadjs: [
+ {
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'users',
+ display: function(item) {
+ return item.first_name + ' ' + item.last_name;
+ },
+ templates: {
+ suggestion: function (item) {
+ return '' + item.first_name + ' ' + item.last_name + '
';
+ }
+ },
+ source: users,
+ limit: 10
+ }
+ ]
+ });
+
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/admin/clients.js b/assets/js/oauth2/profile/admin/clients.js
new file mode 100644
index 00000000..9279b71e
--- /dev/null
+++ b/assets/js/oauth2/profile/admin/clients.js
@@ -0,0 +1,42 @@
+jQuery(document).ready(function($){
+
+ $('#server-admin','#main-menu').addClass('active');
+
+ if($('#clients-table tr').length===1){
+ $('#clients-info').show();
+ $('#clients-table').hide();
+ }
+ else{
+ $('#clients-info').hide();
+ $('#clients-table').show();
+ }
+
+ $("body").on('click',".unlock-client",function(event){
+ if(confirm("Are you sure that you want to unlock this OAUTH2 Client?")){
+ var url = $(this).attr('href');
+ var client_id = $(this).attr('data-client-id');
+ $.ajax(
+ {
+ type: "DELETE",
+ url: url,
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ $('#'+client_id,'#body-locked-clients').remove();
+
+ if($('#clients-table tr').length===1){
+ $('#clients-info').show();
+ $('#clients-table').hide();
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/admin/edit-api-scope-group.js b/assets/js/oauth2/profile/admin/edit-api-scope-group.js
new file mode 100644
index 00000000..8602ea64
--- /dev/null
+++ b/assets/js/oauth2/profile/admin/edit-api-scope-group.js
@@ -0,0 +1,150 @@
+$(document).ready(function() {
+
+ // scopes
+
+ var scopes = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: ApiScopeGroupUrls.fetchScopes,
+ wildcard: '%QUERY%',
+ prepare: function (query, settings) {
+ settings.url = ApiScopeGroupUrls.fetchScopes+'?filter[]=name=@'+query+'&filter[]=is_assigned_by_groups==1';
+ return settings;
+ },
+ transform: function(input){
+ var page = input.data;
+ return page;
+ }
+ }
+ });
+
+ $('#scopes').tagsinput({
+ itemValue: function(item) {
+ return item.id;
+ },
+ itemText: function(item) {
+ return item.name
+ },
+ freeInput: false,
+ allowDuplicates: false,
+ typeaheadjs: [
+ {
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'scopes',
+ display: function(item) {
+ return item.name;
+ },
+ templates: {
+ suggestion: function (item) {
+ return '' + item.name + '
';
+ }
+ },
+ source: scopes,
+ limit: 10
+ }
+ ]
+ });
+
+ for(var scope of current_scopes)
+ {
+ $('#scopes').tagsinput('add', scope);
+ }
+
+ // users
+
+ var users = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: ApiScopeGroupUrls.fetchUsers,
+ wildcard: '%QUERY%',
+ prepare: function (query, settings) {
+ settings.url = ApiScopeGroupUrls.fetchUsers+'?filter=first_name=@'+query+',last_name=@'+query+',email=@'+query;
+ return settings;
+ },
+ transform: function(input){
+ var page = input.data;
+ return page;
+ }
+ }
+ });
+
+ $('#users').tagsinput({
+ itemValue: function(item) {
+ return item.id;
+ },
+ itemText: function(item) {
+ return item.first_name + ' ' + item.last_name;
+ },
+ freeInput: false,
+ allowDuplicates: false,
+ typeaheadjs: [
+ {
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'users',
+ display: function(item) {
+ return item.first_name + ' ' + item.last_name;
+ },
+ templates: {
+ suggestion: function (item) {
+ return '' + item.first_name + ' ' + item.last_name + '
';
+ }
+ },
+ source: users,
+ limit: 10
+ }
+ ]
+ });
+
+
+ for(var user of current_users)
+ {
+ $('#users').tagsinput('add',user);
+ }
+
+ var group_form = $('#api-scope-group-form');
+ var group_validator = group_form.validate({
+ rules: {
+ "name" : { required: true, free_text:true,rangelength: [1, 255]},
+ "users" : { required: true },
+ "scopes" : { required: true },
+ }
+ });
+
+ group_form.submit(function( event ) {
+
+ var is_valid = group_form.valid();
+
+ if (is_valid){
+ group_validator.resetForm();
+ var group = group_form.serializeForm();
+ var href = $(this).attr('action');
+ $.ajax(
+ {
+ type: "PUT",
+ url: href,
+ data: JSON.stringify(group),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ displaySuccessMessage('Group Saved!.' , group_form);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/admin/edit-api.js b/assets/js/oauth2/profile/admin/edit-api.js
new file mode 100644
index 00000000..4e540d9e
--- /dev/null
+++ b/assets/js/oauth2/profile/admin/edit-api.js
@@ -0,0 +1,462 @@
+function loadScopes(){
+ $.ajax({
+ type: "GET",
+ url: scopesUrls.get,
+ contentType: "application/json; charset=utf-8",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ var items = page.data;
+ if(items.length > 0){
+ $('#info-scopes').hide();
+ $('#table-scopes').show();
+ var template = $('' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ 'Edit ' +
+ 'Delete ' +
+ ' ');
+ var directives = {
+ 'tr':{
+ 'scope<-context':{
+ 'td.name':'scope.name',
+ //active
+ '.scope-active-checkbox@value':'scope.id',
+ '.scope-active-checkbox@checked':function(arg){
+ return arg.item.active?'true':'';
+ },
+ '.scope-active-checkbox@id':function(arg){
+ var id = arg.item.id;
+ return 'scope-active_'+id;
+ },
+ '.scope-active-checkbox@data-scope-id':'scope.id',
+ //default
+ '.scope-default-checkbox@value':'scope.id',
+ '.scope-default-checkbox@checked':function(arg){
+ return arg.item.default?'true':'';
+ },
+ '.scope-default-checkbox@id':function(arg){
+ var id = arg.item.id;
+ return 'scope-default_'+id;
+ },
+ '.scope-default-checkbox@data-scope-id':'scope.id',
+ //system
+ '.scope-system-checkbox@value':'scope.id',
+ '.scope-system-checkbox@checked':function(arg){
+ return arg.item.system?'true':'';
+ },
+ '.scope-system-checkbox@id':function(arg){
+ var id = arg.item.id;
+ return 'scope-system_'+id;
+ },
+ '.scope-system-checkbox@data-scope-id':'scope.id',
+ //buttons
+ 'a.edit-scope@href':function(arg){
+ var id = arg.item.id;
+ var href = scopesUrls.edit;
+ return href.replace('@id',id);
+ },
+ 'a.delete-scope@href':function(arg){
+ var id = arg.item.id;
+ var href = scopesUrls.delete;
+ return href.replace('@id',id);
+ }
+ }
+ }
+ };
+ var html = template.render(items, directives);
+ $('#body-scopes').html(html.html());
+ }
+ else{
+ $('#info-scopes').show();
+ $('#table-scopes').hide();
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+}
+
+function loadEndpoints(){
+ $.ajax({
+ type: "GET",
+ url: endpointUrls.get,
+ contentType: "application/json; charset=utf-8",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ var items = page.data;
+ if(items.length>0){
+ $('#info-endpoints').hide();
+ $('#table-endpoints').show();
+ var template = $('' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ 'Edit ' +
+ 'Delete ' +
+ ' ');
+ var directives = {
+ 'tr':{
+ 'endpoint<-context':{
+ 'td.name':'endpoint.name',
+ 'td.route':'endpoint.route',
+ 'td.method':'endpoint.http_method',
+ //active
+ '.endpoint-active-checkbox@value':'scope.id',
+ '.endpoint-active-checkbox@checked':function(arg){
+ return arg.item.active?'true':'';
+ },
+ '.endpoint-active-checkbox@id':function(arg){
+ var id = arg.item.id;
+ return 'endpoint-active_'+id;
+ },
+ '.endpoint-active-checkbox@data-endpoint-id':'endpoint.id',
+ //buttons
+ 'a.edit-endpoint@href':function(arg){
+ var id = arg.item.id;
+ var href = endpointUrls.edit;
+ return href.replace('@id',id);
+ },
+ 'a.delete-endpoint@href':function(arg){
+ var id = arg.item.id;
+ var href = endpointUrls.delete;
+ return href.replace('@id',id);
+ }
+ }
+ }
+ };
+ var html = template.render(items, directives);
+ $('#body-endpoints').html(html.html());
+ }
+ else{
+ $('#info-endpoints').show();
+ $('#table-endpoints').hide();
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+}
+
+$(document).ready(function() {
+
+ $('#server-admin','#main-menu').addClass('active');
+
+ var api_form = $('#api-form');
+ var api_validator = api_form.validate({
+ rules: {
+ "name" : {required: true, nowhitespace:true,rangelength: [1, 255]},
+ "description": {required: true, free_text:true,rangelength: [1, 512]}
+ }
+ });
+
+ api_form.submit(function( event ) {
+ var is_valid = api_form.valid();
+ if (is_valid){
+ api_validator.resetForm();
+ var api = api_form.serializeForm();
+ var href = $(this).attr('action');
+ $.ajax(
+ {
+ type: "PUT",
+ url: href,
+ data: JSON.stringify(api),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ displaySuccessMessage(editApiMessages.success,api_form);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ //scopes
+
+ if($('#table-scopes tr').length===1){
+ $('#info-scopes').show();
+ $('#table-scopes').hide();
+ }
+
+ $("body").on('click','.scope-active-checkbox',function(event){
+ var id = $(this).attr('data-scope-id');
+ var active = $(this).is(':checked');
+
+ var url = active? scopesUrls.activate : scopesUrls.deactivate;
+ url = url.replace('@id', id);
+ var verb = active?'PUT':'DELETE'
+
+ $.ajax(
+ {
+ type: verb,
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ $("body").on('click','.scope-default-checkbox',function(event){
+ var id = $(this).attr('data-scope-id');
+ var is_default = $(this).is(':checked');
+ var url = scopesUrls.update;
+ url = url.replace('@id', id);
+ var scope = { id : id, default:is_default};
+
+ $.ajax(
+ {
+ type: "PUT",
+ url: url,
+ data: JSON.stringify(scope),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ $("body").on('click','.scope-system-checkbox',function(event){
+ var id = $(this).attr('data-scope-id');
+ var is_system = $(this).is(':checked');
+ var url = scopesUrls.update;
+ url = url.replace('@id', id);
+ var scope = { id : id, system:is_system};
+
+ $.ajax(
+ {
+ type: "PUT",
+ url: url,
+ data: JSON.stringify(scope),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+
+ });
+
+ $("body").on('click','.refresh-scopes',function(event){
+ loadScopes();
+ event.preventDefault();
+ return false;
+ });
+
+
+ var scope_dialog = $('#dialog-form-scope');
+ var scope_form = $('#form-scope');
+
+ var scope_validator = scope_form.validate({
+ rules: {
+ "name" : {required: true, scopename:true,rangelength: [1, 512]},
+ "short_description": {required: true, free_text:true,rangelength: [1, 512]},
+ "description": {required: true, free_text:true,rangelength: [1, 1024]}
+ }
+ });
+
+ scope_dialog.modal({
+ show:false,
+ backdrop:"static"
+ });
+
+ scope_dialog.on('hidden.bs.modal', function () {
+ scope_form.cleanForm();
+ scope_validator.resetForm();
+ })
+
+ $("body").on('click',".add-scope",function(event){
+ scope_dialog.modal('show');
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".save-scope",function(event){
+ var is_valid = scope_form.valid();
+ if (is_valid){
+ var scope = scope_form.serializeForm();
+ scope.api_id = api_id;
+ $.ajax(
+ {
+ type: "POST",
+ url: scopesUrls.add,
+ data: JSON.stringify(scope),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadScopes();
+ scope_dialog.modal('hide');
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".delete-scope",function(event){
+ if(confirm("Are you sure? this will delete all application assigned scopes and all endpoints assigned scopes too.")){
+ var url = $(this).attr('href');
+ $.ajax(
+ {
+ type: "DELETE",
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadScopes();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ //endpoints
+
+ var endpoint_dialog = $('#dialog-form-endpoint');
+ var endpoint_form = $('#form-endpoint');
+
+ if($('#table-endpoints tr').length===1){
+ $('#info-endpoints').show();
+ $('#table-endpoints').hide();
+ }
+
+ $("body").on('click','.endpoint-active-checkbox',function(event){
+ var id = $(this).attr('data-endpoint-id');
+ var active = $(this).is(':checked');
+ var url = active? endpointUrls.activate : endpointUrls.deactivate;
+ url = url.replace('@id',id);
+ var verb = active?'PUT':'DELETE';
+ $.ajax(
+ {
+ type: verb,
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ $("body").on('click','.refresh-endpoints',function(event){
+ loadEndpoints();
+ event.preventDefault();
+ return false;
+ });
+
+ var endpoint_validator = endpoint_form.validate({
+ rules: {
+ "name" : {required: true, nowhitespace:true,rangelength: [1, 255]},
+ "description":{required: true, free_text:true,rangelength: [1, 1024]},
+ "route": {required: true,endpointroute:true,rangelength: [1, 1024]},
+ "rate_limit": {required: true, number:true}
+ }
+ });
+
+ endpoint_dialog.modal({
+ show:false,
+ backdrop:"static"
+ });
+
+ endpoint_dialog.on('hidden.bs.modal', function () {
+ endpoint_form.cleanForm();
+ endpoint_validator.resetForm();
+ })
+
+ $("body").on('click',".add-endpoint",function(event){
+ endpoint_dialog.modal('show');
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".save-endpoint",function(event){
+ var is_valid = endpoint_form.valid();
+ if (is_valid){
+ var endpoint = endpoint_form.serializeForm();
+ endpoint.api_id = api_id;
+ $.ajax(
+ {
+ type: "POST",
+ url: endpointUrls.add,
+ data: JSON.stringify(endpoint),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadEndpoints();
+ endpoint_dialog.modal('hide');
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".delete-endpoint",function(event){
+ if(confirm("Are you sure? this will delete the selected endpoint.")){
+ var url = $(this).attr('href');
+ $.ajax(
+ {
+ type: "DELETE",
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadEndpoints();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/admin/edit-endpoint.js b/assets/js/oauth2/profile/admin/edit-endpoint.js
new file mode 100644
index 00000000..cf45ab2f
--- /dev/null
+++ b/assets/js/oauth2/profile/admin/edit-endpoint.js
@@ -0,0 +1,65 @@
+jQuery(document).ready(function($){
+
+ $('#server-admin','#main-menu').addClass('active');
+
+ var endpoint_form = $('#endpoint-form');
+
+ var endpoint_validator = endpoint_form.validate({
+ rules: {
+ "name" : {required: true, nowhitespace:true,rangelength: [1, 255]},
+ "description":{required: true, free_text:true,rangelength: [1, 1024]},
+ "route": {required: true, endpointroute:true,rangelength: [1, 1024]},
+ "rate_limit": {required: true, number:true},
+ "http_method": {required: true}
+ }
+ });
+
+ endpoint_form.submit(function( event ) {
+ var is_valid = endpoint_form.valid();
+ if (is_valid){
+ endpoint_validator.resetForm();
+ var endpoint = endpoint_form.serializeForm();
+ var href = $(this).attr('action');
+ $.ajax(
+ {
+ type: "PUT",
+ url: href,
+ data: JSON.stringify(endpoint),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ displaySuccessMessage(editEndpointMessages.success , endpoint_form);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+ //scopes associations
+ $("body").on('click',".scope-checkbox",function(event){
+ var add_link = $(this).attr('data-add-link');
+ var del_link = $(this).attr('data-remove-link');
+ var checked = $(this).is(':checked');
+ var url = checked?add_link:del_link;
+ var verb = checked?'PUT':'DELETE';
+ $.ajax(
+ {
+ type: verb,
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/admin/edit-resource-server.js b/assets/js/oauth2/profile/admin/edit-resource-server.js
new file mode 100644
index 00000000..33d1a086
--- /dev/null
+++ b/assets/js/oauth2/profile/admin/edit-resource-server.js
@@ -0,0 +1,260 @@
+
+function loadApis(){
+ $.ajax({
+ type: "GET",
+ url: ApiUrls.get,
+ contentType: "application/json; charset=utf-8",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ var items = page.data;
+ if(items.length > 0){
+ $('#info-apis').hide();
+ $('#table-apis').show();
+ var template = $(' Edit Delete ');
+ var directives = {
+ 'tr':{
+ 'api<-context':{
+ 'img@src':function(arg){
+ var logo = arg.item.logo;
+ if(logo == null || logo=='') logo = "/assets/img/apis/server.png";
+ return logo;
+ },
+ 'img@alt':'api.name',
+ 'td.name':'api.name',
+ '.api-active-checkbox@value':'api.id',
+ '.api-active-checkbox@checked':function(arg){
+ return arg.item.active?'true':'';
+ },
+ '.api-active-checkbox@id':function(arg){
+ var id = arg.item.id;
+ return 'resource-server-api-active_'+id;
+ },
+ '.api-active-checkbox@data-api-id':'api.id',
+ 'a.edit-api@href':function(arg){
+ var id = arg.item.id;
+ var href = ApiUrls.edit;
+ return href.replace('-1',id);
+ },
+ 'a.delete-api@href':function(arg){
+ var id = arg.item.id;
+ var href = ApiUrls.delete;
+ return href.replace('-1',id);
+ }
+ }
+ }
+ };
+ var html = template.render(items, directives);
+ $('#body-apis').html(html.html());
+ }
+ else{
+ $('#info-apis').show();
+ $('#table-apis').hide();
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+}
+
+jQuery(document).ready(function($){
+
+ $('#server-admin','#main-menu').addClass('active');
+
+ if($('#table-apis tr').length === 1)
+ {
+ $('#info-apis').show();
+ $('#table-apis').hide();
+ }
+
+ $("body").on('click','.refresh-apis',function(event){
+ loadApis();
+ event.preventDefault();
+ return false;
+ });
+
+ var resource_server_form = $('#resource-server-form');
+
+ var api_form = $('#form-api');
+ var api_dialog = $('#dialog-form-api');
+
+ api_dialog.modal({
+ show:false,
+ backdrop:"static"
+ });
+
+ var resource_server_validator = resource_server_form.validate({
+ rules: {
+ "host" : {required: true},
+ "friendly_name": {required: true, free_text:true,rangelength: [1, 255]},
+ "ips": {required: true, ipv4V6Set: true }
+ }
+ });
+
+ var api_validator = api_form.validate({
+ rules: {
+ "name" : {required: true, nowhitespace:true,rangelength: [1, 255]},
+ "description": {required: true, free_text:true,rangelength: [1, 512]}
+ }
+ });
+
+ $('#host').tagsinput({
+ trimValue: true,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ allowDuplicates: false
+ });
+
+ $('#ips').tagsinput({
+ trimValue: true,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ allowDuplicates: false
+ });
+
+ $('#ips').on('beforeItemAdd', function(event) {
+ var ip = event.item;
+ var valid = regex_ipv4.test(ip);
+ if(!valid)
+ valid = regex_ipv6.test(ip);
+ if(!valid)
+ event.cancel = true;
+ });
+
+ api_dialog.on('hidden.bs.modal', function () {
+ api_form.cleanForm();
+ api_validator.resetForm();
+ })
+
+ $("body").on('click','.save-api',function(event){
+ var is_valid = api_form.valid();
+ if (is_valid){
+ var api = api_form.serializeForm();
+ api.resource_server_id = resource_server_id;
+ $.ajax({
+ type: "POST",
+ url: ApiUrls.add,
+ data: JSON.stringify(api),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadApis();
+ api_dialog.modal('hide');
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ resource_server_form.submit(function( event ) {
+
+ var is_valid = resource_server_form.valid();
+
+ if (is_valid){
+ resource_server_validator.resetForm();
+ var resource_server = resource_server_form.serializeForm();
+ var href = $(this).attr('action');
+ $.ajax(
+ {
+ type: "PUT",
+ url: href,
+ data: JSON.stringify(resource_server),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ displaySuccessMessage(resourceServerMessages.success , resource_server_form);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".add-api",function(event){
+ api_dialog.modal('show');
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".api-active-checkbox",function(event){
+ var active = $(this).is(':checked');
+ var api_id = $(this).attr('data-api-id');
+ var url = active? ApiUrls.activate : ApiUrls.deactivate;
+ url = url.replace('@id',api_id);
+ var verb = active?'PUT':'DELETE';
+ $.ajax(
+ {
+ type: verb,
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ $("body").on('click',".delete-api",function(event){
+ if(confirm("Are you sure? this would delete all related registered endpoints and associated scopes.")){
+ var href = $(this).attr('href');
+ $.ajax(
+ {
+ type: "DELETE",
+ url: href,
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadApis();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".regenerate-client-secret",function(event){
+ if(confirm("Are you sure? Regenerating client secret would invalidate all current tokens")){
+ var link = $(this).attr('href');
+ $.ajax(
+ {
+ type: "PUT",
+ url: link,
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ $('#client_secret').text(data.client_secret);
+ //clean token UI
+ $('#table-access-tokens').remove();
+ $('#table-refresh-tokens').remove();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/admin/edit-scope.js b/assets/js/oauth2/profile/admin/edit-scope.js
new file mode 100644
index 00000000..397cfb4f
--- /dev/null
+++ b/assets/js/oauth2/profile/admin/edit-scope.js
@@ -0,0 +1,41 @@
+$(document).ready(function() {
+
+ $('#server-admin','#main-menu').addClass('active');
+
+ var scope_form = $('#scope-form');
+
+ var scope_validator = scope_form.validate({
+ rules: {
+ "name" : {required: true, scopename:true,rangelength: [1, 512]},
+ "short_description": {required: true, free_text:true,rangelength: [1, 512]},
+ "description": {required: true, free_text:true,rangelength: [1, 1024]}
+ }
+ });
+
+ scope_form.submit(function( event ) {
+ var is_valid = scope_form.valid();
+ if (is_valid){
+ scope_validator.resetForm();
+ var scope = scope_form.serializeForm();
+ var href = $(this).attr('action');
+ $.ajax(
+ {
+ type: "PUT",
+ url: href,
+ data: JSON.stringify(scope),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ displaySuccessMessage(editScopeMessages.success,scope_form);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+});
diff --git a/assets/js/oauth2/profile/admin/resource-servers.js b/assets/js/oauth2/profile/admin/resource-servers.js
new file mode 100644
index 00000000..0409e62c
--- /dev/null
+++ b/assets/js/oauth2/profile/admin/resource-servers.js
@@ -0,0 +1,181 @@
+function loadResourceServers(){
+ var link = resourceServerUrls.get;
+ $.ajax(
+ {
+ type: "GET",
+ url: link,
+ dataType: "json",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ //load data...
+ var items = page.data;
+ var template = $(' Edit Delete ');
+ var directives = {
+ 'tr':{
+ 'resource_server<-context':{
+ 'td.fname':'resource_server.friendly_name',
+ 'td.hname':'resource_server.host',
+ 'td.ips':'resource_server.ips',
+ '.resource-server-active-checkbox@value':'resource_server.id',
+ '.resource-server-active-checkbox@checked':function(arg){
+ return arg.item.active?'true':'';
+ },
+ '.resource-server-active-checkbox@data-resource-server-id':'resource_server.id',
+ '.resource-server-active-checkbox@id':function(arg){
+ var id = arg.item.id;
+ return 'resource-server-active_'+id;
+ },
+ 'a.edit-resource-server@href':function(arg){
+ var id = arg.item.id;
+ var href = resourceServerUrls.edit;
+ return href.replace('-1',id);
+ },
+ 'a.delete-resource-server@href':function(arg){
+ var id = arg.item.id;
+ var href = resourceServerUrls.delete;
+ return href.replace('-1',id);
+ }
+ }
+ }
+ };
+ var html = template.render(items, directives);
+ $('#body-resource-servers').html(html.html());
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+}
+
+$(document).ready(function() {
+
+ $('#server-admin','#main-menu').addClass('active');
+
+ //validation rules on new server form
+ var resource_server_form = $('#form-resource-server');
+ var dialog_resource_server = $('#dialog-form-resource-server');
+
+ var resource_server_validator = resource_server_form.validate({
+ rules: {
+ "host" : {required: true},
+ "friendly_name": {required: true, free_text:true, rangelength: [1, 255]},
+ "ips": {required: true},
+ }
+ });
+
+ dialog_resource_server.modal({
+ show:false,
+ backdrop:"static"
+ });
+
+ $('#host').tagsinput({
+ trimValue: true,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ allowDuplicates: false
+ });
+
+ $('#ips').tagsinput({
+ trimValue: true,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ allowDuplicates: false
+ });
+
+ $('#ips').on('beforeItemAdd', function(event) {
+ var ip = event.item;
+ var valid = regex_ipv4.test(ip);
+ if(!valid)
+ valid = regex_ipv6.test(ip);
+ if(!valid)
+ event.cancel = true;
+ });
+
+ dialog_resource_server.on('hidden', function () {
+ resource_server_form.cleanForm();
+ resource_server_validator.resetForm();
+ })
+
+ $("body").on('click',".add-resource-server",function(event){
+ dialog_resource_server.modal('show');
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".refresh-servers",function(event){
+ loadResourceServers()
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".resource-server-active-checkbox",function(event){
+ var active = $(this).is(':checked');
+ var resource_server_id = $(this).attr('data-resource-server-id');
+ var url = active? resourceServerUrls.activate : resourceServerUrls.deactivate;
+ url = url.replace('@id',resource_server_id);
+ var verb = active?'PUT':'DELETE';
+ $.ajax(
+ {
+ type: verb,
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ $("body").on('click',".save-resource-server",function(event){
+ var is_valid = resource_server_form.valid();
+ if (is_valid){
+ var resource_server = resource_server_form.serializeForm();
+ $.ajax({
+ type: "POST",
+ url: resourceServerUrls.add,
+ data: JSON.stringify(resource_server),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadResourceServers();
+ dialog_resource_server.modal('hide');
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".delete-resource-server",function(event){
+ if(confirm("Are you sure? this would delete all related registered apis, endpoints and associated scopes.")){
+ var href = $(this).attr('href');
+ $.ajax(
+ {
+ type: "DELETE",
+ url: href,
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadResourceServers();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/admin/server-private-keys.js b/assets/js/oauth2/profile/admin/server-private-keys.js
new file mode 100644
index 00000000..9cd5ccf7
--- /dev/null
+++ b/assets/js/oauth2/profile/admin/server-private-keys.js
@@ -0,0 +1,320 @@
+(function( $ ){
+
+ $(document).ready(function($){
+
+ var options = {};
+
+ options.ui =
+ {
+ showPopover: false,
+ showErrors: true,
+ showProgressBar: true,
+ showVerdictsInsideProgressBar: true,
+ };
+
+ options.rules =
+ {
+ activated: {
+ wordTwoCharacterClasses: true,
+ wordRepetitions: true
+ }
+ };
+
+ $(':password').pwstrength(options);
+
+ var modal = $('#ModalAddPrivateKey');
+ modal.modal({show:false});
+
+ // private key form
+ var form = $('#form-add-private-key');
+
+ var validator = form.validate({
+ rules: {
+ "kid" : {
+ required: true,
+ free_text : true,
+ maxlength:255,
+ minlength: 5
+ },
+ "valid_from": {
+ required: true,
+ dateUS:true
+ },
+ "valid_to": {
+ required: true ,
+ dateUS:true
+ },
+ "pem_content" : { pem_private_key : function(element){
+ var autogenerate = $("#autogenerate").is(':checked');
+ return autogenerate? false: true;
+ }},
+ password: {
+ private_key_password_required: {pem_content_id: '#pem_content'},
+ minlength: 5
+ },
+ "password-confirmation" : {
+ minlength: 5,
+ equalTo : "#password"
+ },
+ "alg" : {required: true}
+ }
+ });
+
+ modal.on('shown.bs.modal', function (e) {
+ $("#form-add-private-key .date-picker").datepicker({
+ startDate: "today",
+ todayBtn: "linked",
+ clearBtn: true,
+ todayHighlight: true,
+ //orientation: "bottom right",
+ autoclose: true
+ });
+ })
+
+ modal.on('hidden.bs.modal', function () {
+ form.cleanForm();
+ validator.resetForm();
+ })
+
+ $("body").on('click',".add-private-key",function(event){
+ modal.modal('show');
+ validator.resetForm();
+ $(":password").pwstrength("forceUpdate");
+ $('#autogenerate').prop('checked', true);
+ $('#active').prop('checked', true);
+ $("#pem_container").hide();
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',"#autogenerate",function(event){
+ var autogenerate = $(this).is(':checked');
+ if(autogenerate){
+ $("#pem_container").hide();
+ }
+ else{
+ $("#pem_container").show();
+ }
+ });
+
+ $("body").on('click',".delete-private-key",function(event){
+ if(window.confirm('are you sure?')){
+ //delete key
+ var private_key_id = $(this).attr('data-private-key-id');
+
+ $.ajax(
+ {
+ type: "DELETE",
+ url: privateKeyUrls.delete.replace('@id', private_key_id),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ $('#tr_'+private_key_id).fadeOut(300, function() {
+ $(this).remove();
+ if($('#body-private-keys').children('tr').length)
+ $('.private-keys-empty-message').hide();
+ else
+ $('.private-keys-empty-message').show();
+ });
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".save-private-key",function(event){
+
+ if(form.valid()) {
+ var private_key_data = form.serializeForm();
+ private_key_data.type = 'RSA';
+
+ if(private_key_data.autogenerate)
+ delete private_key_data.pem_content;
+
+ $.ajax(
+ {
+ type: "POST",
+ url: privateKeyUrls.add,
+ data: JSON.stringify(private_key_data),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ modal.modal('hide');
+ form.cleanForm();
+
+ $('.private-keys-empty-message').hide();
+ loadPrivateKeys();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".private-key-status",function(event){
+
+ var status_badge = $(this);
+ var private_key_id = status_badge.attr('data-private-key-id');
+ var private_key_data = { id : private_key_id };
+ private_key_data.active = status_badge.hasClass('private-key-active') ? false : true;
+
+ $.ajax(
+ {
+ type: "PUT",
+ url: privateKeyUrls.update.replace('@id', private_key_id),
+ contentType: "application/json; charset=utf-8",
+ data: JSON.stringify(private_key_data),
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ loadPrivateKeys();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+
+ event.preventDefault();
+ return false;
+ });
+
+ $('#usage').change(function(){
+
+ var usage = $(this).val();
+
+ var alg_select = $('#alg');
+
+ alg_select.empty();
+
+ var result = [];
+
+ if(usage === 'sig')
+ {
+ result = oauth2_supported_algorithms.sig_algorihtms.rsa;
+ }
+ else
+ {
+ result = oauth2_supported_algorithms.key_management_algorihtms;
+ }
+
+ $.each(result, function(index, item) {
+ var key = item === 'none' ? '' : item;
+ alg_select.append($(" ").val(key).text(item));
+ });
+ });
+
+ $('#usage').trigger('change');
+
+ });
+
+ function loadPrivateKeys(){
+
+ $.ajax({
+ type: "GET",
+ url: privateKeyUrls.get,
+ dataType: "json",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ //load data...
+ var private_keys = page.data;
+
+ if(private_keys.length > 0){
+
+ var template = $('' +
+ ''+
+ ''+
+ ''+
+ '
'+
+ '  '+
+ '
'+
+ '
'+
+ ' '+
+ '
'+
+ '
'+
+ ' '+
+ ''+
+ ''+
+ ' '+
+ 'Delete '+
+ ' '+
+ ' ');
+
+ var directives = {
+ 'tr':{
+ 'private_key<-context':{
+ '.private-key-status@title':function(arg){
+ return arg.item.active ? 'active': 'deactivated';
+ },
+ '.private-key-status@data-private-key-id': 'private_key.id',
+ '.private-key-status@class+':function(arg){
+ return arg.item.active ? ' private-key-active': ' private-key-deactivated';
+ },
+ '.fa-key@title':function(arg){
+ return arg.item.kid+' ('+arg.item.type+')';
+ },
+ '.delete-private-key@data-private-key-id': 'private_key.id',
+ '.private-key-validity-range':function(arg){
+ return 'valid from '+moment.unix(arg.item.valid_from).format()+' to '+moment.unix(arg.item.valid_to).format()+' ';
+ },
+ '.private-key-fingerprint' : 'private_key.sha_256',
+ '.private-key-title' : function(arg){
+ var usage = ''+arg.item.usage+' ';
+ var type = ''+arg.item.type+' ';
+ var alg = ''+arg.item.alg+' ';
+ return arg.item.kid+' '+usage+' '+type+' '+alg;
+ },
+ '@id':function(arg){
+ return 'tr_'+arg.item.id;
+ }
+ }
+ }
+ };
+
+ var html = template.render(private_keys, directives);
+ $('#body-private-keys').html(html.html());
+ $('.private-keys-empty-message').hide();
+ }
+ else{
+ $('.private-keys-empty-message').show();
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+
+// End of closure.
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/clients.js b/assets/js/oauth2/profile/clients.js
new file mode 100644
index 00000000..7f0eb627
--- /dev/null
+++ b/assets/js/oauth2/profile/clients.js
@@ -0,0 +1,254 @@
+
+function loadClients(){
+ $.ajax({
+ type: "GET",
+ url: clientsUrls.load,
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ //load data...
+ var clients = page.data;
+ var template = $(' Edit Delete ');
+ var directives = {
+ 'tr':{
+ 'client<-context':{
+ 'td.admin-app':function(arg){
+ return arg.item.is_own?'':' ';
+ },
+ 'td.app-name':'client.app_name',
+ 'td.client-type':'client.friendly_application_type',
+ 'td.client-modified':'client.updated_at',
+ 'td.client-modified-by':'client.modified_by',
+ '.app-active-checkbox@value':'client.id',
+ '.app-active-checkbox@checked':function(arg){
+ return arg.item.active?'true':'';
+ },
+ '.app-active-checkbox@id':function(arg){
+ var client_id = arg.item.id;
+ return 'app-active_'+client_id;
+ },
+ '.app-locked-checkbox@value':'client.id',
+ '.app-locked-checkbox@id':function(arg){
+ var client_id = arg.item.id;
+ return 'app-locked_'+client_id;
+ },
+ '.app-locked-checkbox@checked':function(arg){
+ return arg.item.locked?'true':'';
+ },
+ 'a.edit-client@href':function(arg){
+ var client_id = arg.item.id;
+ var href = clientsUrls.edit;
+ return href.replace('@id',client_id);
+ },
+ 'a.del-client@href':function(arg){
+ var client_id = arg.item.id;
+ var href = clientsUrls.delete;
+ return href.replace('@id',client_id);
+ },
+ 'a.del-client@class+':function(arg)
+ {
+ return arg.item.is_own?'':' hidden';
+ },
+ '.app-active-checkbox@class+':function(arg){
+ return arg.item.is_own?'':' hidden';
+ }
+ }
+ }
+ };
+ var body = template.render(clients, directives);
+ var table = $(' Application Name Type Is Active Is Locked Modified Modified By '+body.html()+'
');
+ $('#tclients','#clients').remove();
+ $('#clients').append(table);
+
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+}
+
+jQuery(document).ready(function($){
+
+ $('#oauth2-console','#main-menu').addClass('active');
+
+ var application_form = $('#form-application');
+ var application_dialog = $("#dialog-form-application");
+
+ var application_validator = application_form.validate({
+ rules: {
+ "app_name" : {required: true, free_text:true, rangelength: [1, 255]},
+ "app_description" : {required: true, free_text:true,rangelength: [1, 512]},
+ "website" : {url:true}
+ }
+ });
+
+ var users = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: clientsUrls.fetchUsers,
+ wildcard: '%QUERY%',
+ prepare: function (query, settings) {
+ settings.url = clientsUrls.fetchUsers+'?filter=first_name=@'+query+',last_name=@'+query+',email=@'+query;
+ return settings;
+ },
+ transform: function(input){
+ var page = input.data;
+ return page;
+ }
+ }
+ });
+
+ $('#admin_users').tagsinput({
+ itemValue: function(item) {
+ return item.id;
+ },
+ itemText: function(item) {
+ return item.first_name + ' ' + item.last_name;
+ },
+ freeInput: false,
+ allowDuplicates: false,
+ typeaheadjs: [
+ {
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'users',
+ display: function(item) {
+ return item.first_name + ' ' + item.last_name;
+ },
+ templates: {
+ suggestion: function (item) {
+ return '' + item.first_name + ' ' + item.last_name + '
';
+ }
+ },
+ source: users,
+ limit: 10
+ }
+ ]
+ });
+
+ application_dialog.modal({
+ show:false,
+ backdrop:"static"
+ });
+
+ application_dialog.on('hidden.bs.modal', function () {
+ application_form.cleanForm();
+ application_validator.resetForm();
+ $('.add-client').removeAttr('disabled');
+ })
+
+ $("body").on('click',".add-client",function(event){
+ application_dialog.modal('show');
+ $('.add-client').attr('disabled','disabled');
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',"#save-application",function(event){
+ var is_valid = application_form.valid();
+ if (is_valid){
+ $('#save-application').attr('disabled','disabled');
+ var application = application_form.serializeForm();
+ application.user_id = userId;
+ if( application.admin_users) {
+ application.admin_users = application.admin_users.split(",");
+ for (var i = 0; i < application.admin_users.length; i++) {
+ application.admin_users[i] = parseInt(application.admin_users[i], 10);
+ }
+ }
+ var link = $(this).attr('href');
+ $.ajax({
+ type: "POST",
+ url: clientsUrls.add,
+ data: JSON.stringify(application),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadClients();
+ application_dialog.modal('hide');
+
+ var client_id = data.client_id;
+ var client_secret = data.client_secret;
+ var credentials_txt = "CLIENT ID
"+client_id+"
";
+ if(client_secret)
+ credentials_txt += "CLIENT SECRET
"+client_secret+"
";
+
+ swal({
+ title: "Your Client Credentials!",
+ html: credentials_txt,
+ type: "info",
+ customClass: "auto-width"
+ });
+
+ $('#save-application').removeAttr('disabled');
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ $('#save-application').removeAttr('disabled');
+ }
+ });
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".del-client",function(event){
+ var url = $(this).attr('href');
+ swal({
+ title: "Are you sure to delete this registered application?",
+ text: "This is an non reversible process!",
+ type: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: "Yes, Delete it!",
+ closeOnConfirm: true
+ }).then(
+ function(result){
+ if(!result) return;
+ $.ajax(
+ {
+ type: "DELETE",
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ loadClients();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".app-active-checkbox",function(event){
+ var active = $(this).is(':checked');
+ var client_id = $(this).attr('value');
+ var url = active? clientsUrls.activate : clientsUrls.deactivate;
+ url = url.replace('@id',client_id);
+ var verb = active?'PUT':'DELETE';
+
+ $.ajax(
+ {
+ type: verb,
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/edit-client-data.js b/assets/js/oauth2/profile/edit-client-data.js
new file mode 100644
index 00000000..a7c961dc
--- /dev/null
+++ b/assets/js/oauth2/profile/edit-client-data.js
@@ -0,0 +1,232 @@
+jQuery(document).ready(function($){
+
+ $('#contacts').tagsinput({
+ trimValue: true,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ allowDuplicates: false
+ });
+
+ $('#contacts').on('beforeItemAdd', function(event) {
+ // event.item: contains the item
+ // event.cancel: set to true to prevent the item getting added
+ var regex_email = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ig;
+ var current = regex_email.test( event.item );
+ if(!current)
+ event.cancel = true;
+ });
+
+ var users = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ remote: {
+ url: dataClientUrls.fetchUsers,
+ wildcard: '%QUERY%',
+ prepare: function (query, settings) {
+ settings.url = dataClientUrls.fetchUsers+'?filter=first_name=@'+query+',last_name=@'+query+',email=@'+query;
+ return settings;
+ },
+ transform: function(input){
+ var page = input.data;
+ return page;
+ }
+ }
+ });
+
+ $('#admin_users').tagsinput({
+ itemValue: function(item) {
+ return item.id;
+ },
+ itemText: function(item) {
+ return item.first_name + ' ' + item.last_name;
+ },
+ freeInput: false,
+ allowDuplicates: false,
+ typeaheadjs: [
+ {
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'users',
+ display: function(item) {
+ return item.first_name + ' ' + item.last_name;
+ },
+ templates: {
+ suggestion: function (item) {
+ return '' + item.first_name + ' ' + item.last_name + '
';
+ }
+ },
+ source: users,
+ limit: 10
+ }
+ ]
+ });
+
+ for(var user of current_admin_users)
+ {
+ $('#admin_users').tagsinput('add',user);
+ }
+
+ $('#redirect_uris').tagsinput({
+ trimValue: true,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ allowDuplicates: false
+ });
+
+ $('#redirect_uris').on('beforeItemAdd', function(event) {
+ var uri = new URI(event.item);
+ var app_type = $('#application_type').val();
+ var valid = app_type == 'NATIVE' ? true : uri.protocol() === 'https' ;
+ var valid = valid && uri.is('url') && uri.is('absolute') && uri.search() == '' && uri.fragment() == ''
+ if(!valid)
+ event.cancel = true;
+ });
+
+ $('#allowed_origins').tagsinput({
+ trimValue: true,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ allowDuplicates: false
+ });
+
+ $('#allowed_origins').on('beforeItemAdd', function(event) {
+
+ var uri = new URI(event.item);
+ var valid = uri.is('url') && uri.is('absolute') && uri.protocol() === 'https' && uri.search() == '' && uri.fragment() == '' ;
+ if(!valid)
+ event.cancel = true;
+ });
+
+ $("body").on('click',".regenerate-client-secret",function(event){
+ var link = $(this).attr('href');
+ swal({
+ title: "Are you sure?",
+ text: "Regenerating client secret would invalidate all current tokens!",
+ type: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: "Yes, Regenerate it!",
+ closeOnConfirm: true
+ }).then(
+ function(result){
+ if(!result) return;
+ $.ajax(
+ {
+ type: "PUT",
+ url: link,
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ $('#client_secret > :input').val(data.client_secret);
+ //$('#client_secret_expiration_date').text(data.new_expiration_date.date);
+ //clean token UI
+ $('#table-access-tokens').remove();
+ $('#table-refresh-tokens').remove();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',"#use-refresh-token",function(event){
+ var use_refresh_token = $(this).is(':checked');
+ $.ajax(
+ {
+ type: "PUT",
+ url: decodeURI(dataClientUrls.refresh).replace('@use_refresh_token', use_refresh_token),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ $("body").on('click',"#use-rotate-refresh-token-policy",function(event){
+ var rotate_refresh_token = $(this).is(':checked');
+ $.ajax(
+ {
+ type: "PUT",
+ url: decodeURI(dataClientUrls.rotate).replace('@rotate_refresh_token', rotate_refresh_token),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ var form = $('#form-application-main-data');
+
+ var validator = form.validate({
+ rules: {
+ app_name : {required: true, free_text:true, rangelength: [1, 255]},
+ app_description : {required: true, free_text:true,rangelength: [1, 512]},
+ website: {url: true},
+ logo_uri: {url: true},
+ tos_uri: {url: true},
+ policy_uri: {url: true},
+ }
+ });
+
+ form.submit(function(e){
+ var is_valid = $(this).valid();
+ if (is_valid) {
+ $('.btn-save-client-data').attr('disabled','disabled');
+ var application = $(this).serializeForm();
+ if(is_mine) {
+ var admin_users = application.admin_users;
+ delete application.admin_users;
+ if (admin_users != '') {
+ admin_users = admin_users.split(",");
+ for (var i = 0; i < admin_users.length; i++) {
+ admin_users[i] = parseInt(admin_users[i], 10);
+ }
+ application.admin_users = admin_users;
+ }
+ }
+ $.ajax(
+ {
+ type: "PUT",
+ url: dataClientUrls.update,
+ data: JSON.stringify(application),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ $('.btn-save-client-data').removeAttr('disabled');
+ displaySuccessMessage('Data saved successfully.', form);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ $('.btn-save-client-data').removeAttr('disabled');
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ e.preventDefault();
+ return false;
+ });
+
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/edit-client-public-keys.js b/assets/js/oauth2/profile/edit-client-public-keys.js
new file mode 100644
index 00000000..f9e322ff
--- /dev/null
+++ b/assets/js/oauth2/profile/edit-client-public-keys.js
@@ -0,0 +1,277 @@
+(function( $ ){
+
+ $(document).ready(function($){
+
+ var modal = $('#ModalAddPublicKey');
+ modal.modal({show:false});
+
+ $('#form-add-public-key .input-daterange').datepicker({
+ startDate: "today",
+ todayBtn: "linked",
+ clearBtn: true,
+ todayHighlight: true,
+ orientation: "bottom right",
+ autoclose: true
+ });
+
+ // public key form
+ var form = $('#form-add-public-key');
+
+ var validator = form.validate({
+ rules: {
+ "kid" : {
+ required: true,
+ free_text : true,
+ maxlength:255,
+ minlength: 5
+ },
+ "valid_from": {
+ required: true,
+ dateUS:true
+ },
+ "valid_to": {
+ required: true ,
+ dateUS:true
+ },
+ "pem_content" : {
+ required: true,
+ pem_public_key : true
+ },
+ "alg" : {required: true}
+ }
+ });
+
+ $('#usage').change(function(){
+
+ var usage = $(this).val();
+
+ var alg_select = $('#alg');
+
+ alg_select.empty();
+
+ var result = [];
+
+ if(usage === 'sig')
+ {
+ result = oauth2_supported_algorithms.sig_algorihtms.rsa;
+ }
+ else
+ {
+ result = oauth2_supported_algorithms.key_management_algorihtms;
+ }
+
+ $.each(result, function(index, item) {
+ var key = item === 'none' ? '' : item;
+ alg_select.append($(" ").val(key).text(item));
+ });
+ });
+
+ $('#usage').trigger('change');
+
+ $("body").on('click',".add-public-key",function(event){
+ modal.modal('show');
+ validator.resetForm();
+ $('#active').prop('checked', true);
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".delete-public-key",function(event){
+ var public_key_id = $(this).attr('data-public-key-id');
+ swal({
+ title: "Are you sure?",
+ text: "Deleting this public key is a non reversible operation!",
+ type: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: "Yes, Delete it!",
+ closeOnConfirm: true
+ }).thern(
+ function(result){
+ if(!result) return;
+ $.ajax(
+ {
+ type: "DELETE",
+ url: dataClientUrls.delete_public_key.replace('@public_key_id', public_key_id),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ $('#tr_'+public_key_id).fadeOut(300, function() {
+ $(this).remove();
+ if($('#body-public-keys').children('tr').length)
+ $('.public-keys-empty-message').hide();
+ else
+ $('.public-keys-empty-message').show();
+ });
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".save-public-key",function(event){
+
+ if(form.valid()) {
+
+ var public_key_data = form.serializeForm();
+ public_key_data.type = 'RSA';
+ $.ajax(
+ {
+ type: "POST",
+ url: dataClientUrls.add_public_key,
+ data: JSON.stringify(public_key_data),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ modal.modal('hide');
+ form.cleanForm();
+ $('.public-keys-empty-message').hide();
+ loadPublicKeys();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click',".public-key-status",function(event){
+
+ var status_badge = $(this);
+ var public_key_id = status_badge.attr('data-public-key-id');
+ var public_key_data = { id : public_key_id };
+ public_key_data.active = status_badge.hasClass('public-key-active') ? false : true;
+
+ $.ajax(
+ {
+ type: "PUT",
+ url: dataClientUrls.update_public_key.replace('@public_key_id', public_key_id),
+ contentType: "application/json; charset=utf-8",
+ data: JSON.stringify(public_key_data),
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ loadPublicKeys();
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+
+ event.preventDefault();
+ return false;
+ });
+
+ });
+
+ function loadPublicKeys(){
+
+ $.ajax({
+ type: "GET",
+ url: dataClientUrls.get_public_keys+'?page=1&per_page=4294967296',
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ var public_keys = data.page;
+
+ if(public_keys.length > 0){
+
+ var template = $('' +
+ ''+
+ ''+
+ ''+
+ '
'+
+ '  '+
+ '
'+
+ '
'+
+ ' '+
+ '
'+
+ '
'+
+ ' '+
+ ''+
+ ''+
+ ' '+
+ 'Delete '+
+ ' '+
+ ' ');
+
+ var directives = {
+ 'tr':{
+ 'public_key<-context':{
+ '.public-key-status@title':function(arg){
+ return parseInt(arg.item.active) === 1 ? 'active': 'deactivated';
+ },
+ '.public-key-status@data-public-key-id': 'public_key.id',
+ '.public-key-status@class+':function(arg){
+ return parseInt(arg.item.active) === 1 ? ' public-key-active': ' public-key-deactivated';
+ },
+ '.fa-key@title':function(arg){
+ return arg.item.kid+' ('+arg.item.type+')';
+ },
+ '.delete-public-key@data-public-key-id': 'public_key.id',
+ '.public-key-validity-range':function(arg){
+ return 'valid from '+arg.item.valid_from+' to '+arg.item.valid_to+' ';
+ },
+ //'td.public-key-usage' : 'public_key.usage',
+ '.public-key-fingerprint' : 'public_key.sha_256',
+ '.public-key-title' : function(arg){
+ var usage = ''+arg.item.usage+' ';
+ var type = ''+arg.item.type+' ';
+ var alg = ''+arg.item.alg+' ';
+ return arg.item.kid+' '+usage+' '+type+' '+alg;
+ },
+ '@id':function(arg){
+ return 'tr_'+arg.item.id;
+ }
+ }
+ }
+ };
+
+ var html = template.render(public_keys, directives);
+ $('#body-public-keys').html(html.html());
+ $('.public-keys-empty-message').hide();
+ }
+ else{
+ $('.public-keys-empty-message').show();
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+
+// End of closure.
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/edit-client-scopes.js b/assets/js/oauth2/profile/edit-client-scopes.js
new file mode 100644
index 00000000..ac85b720
--- /dev/null
+++ b/assets/js/oauth2/profile/edit-client-scopes.js
@@ -0,0 +1,28 @@
+jQuery(document).ready(function($){
+ $("body").on('click',".scope-checkbox",function(event){
+ var add_url = clientScopesUrls.add;
+ var remove_url = clientScopesUrls.delete;
+ var scope_id = $(this).attr('value');
+ var checked = $(this).is(':checked');
+ var that = $(this);
+ var url = checked?add_url:remove_url;
+ url = url.replace('@scope_id',scope_id);
+ var verb = checked ? 'PUT' : 'DELETE';
+ $.ajax(
+ {
+ type: verb,
+ url: url,
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ that.attr('checked', false);
+ }
+ }
+ );
+ });
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/edit-client-security-logout.js b/assets/js/oauth2/profile/edit-client-security-logout.js
new file mode 100644
index 00000000..63424b53
--- /dev/null
+++ b/assets/js/oauth2/profile/edit-client-security-logout.js
@@ -0,0 +1,55 @@
+jQuery(document).ready(function($){
+
+ var form = $("#form-application-security-logout");
+
+ $('#post_logout_redirect_uris').tagsinput({
+ trimValue: true,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ allowDuplicates: false
+ });
+
+ $('#post_logout_redirect_uris').on('beforeItemAdd', function(event) {
+ // event.item: contains the item
+ // event.cancel: set to true to prevent the item getting added
+ var regex = /^https:\/\/([\w@][\w.:@]+)\/?[\w\.?=%&=\-@/$,]*$/ig;
+ var current = regex.test(event.item);
+ if(!current)
+ event.cancel = true;
+ });
+
+ var validator = form.validate({
+ rules: {
+ "logout_uri" : {ssl_uri : true}
+ }
+ });
+
+ form.submit(function(e){
+ var is_valid = $(this).valid();
+ if (is_valid) {
+
+ var application_data = $(this).serializeForm();
+
+ $.ajax(
+ {
+ type: "PUT",
+ url: dataClientUrls.update,
+ data: JSON.stringify(application_data),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ displaySuccessMessage('Data saved successfully.', form);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ e.preventDefault();
+ return false;
+ });
+
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/edit-client-security-main-settings.js b/assets/js/oauth2/profile/edit-client-security-main-settings.js
new file mode 100644
index 00000000..655715a0
--- /dev/null
+++ b/assets/js/oauth2/profile/edit-client-security-main-settings.js
@@ -0,0 +1,113 @@
+jQuery(document).ready(function($){
+
+ var form = $("#form-application-security");
+
+ $.validator.addMethod("must_use_alg", function(value, element, options) {
+ if(value === 'none') return true;
+ return $(options.alg_element_id).val() !== 'none';
+ },"You must select an Encrypted Key Algorithm");
+
+ $.validator.addMethod("must_use_enc", function(value, element, options) {
+ if(value === 'none') return true;
+ return $(options.enc_element_id).val() !== 'none';
+ },"You must select an Encrypted Content Algorithm");
+
+
+ var validator = form.validate({
+ rules: {
+ "default_max_age" : {integer : true},
+ "jwks_uri" : {ssl_uri : true},
+ "userinfo_encrypted_response_enc" : { must_use_alg: {alg_element_id:'#userinfo_encrypted_response_alg'}},
+ "id_token_encrypted_response_enc" : { must_use_alg: {alg_element_id:'#id_token_encrypted_response_alg'}},
+ "userinfo_encrypted_response_alg" : { must_use_enc: {enc_element_id:'#userinfo_encrypted_response_enc'}},
+ "id_token_encrypted_response_alg" : { must_use_enc: {enc_element_id:'#id_token_encrypted_response_enc'}}
+ }
+ });
+
+ if($("#otp_enabled") .is(":checked")){
+ $(".otp_controls").removeClass("hidden");
+ $("#otp_length").rules("add", {required:true, min:4, max:8});
+ $("#otp_lifetime").rules("add", {required:true, min:60, max:600});
+ }
+ else {
+ $(".otp_controls").addClass("hidden");
+ $("#otp_length").rules("remove");
+ $("#otp_lifetime").rules("remove");
+ }
+
+
+ $("#otp_enabled").change(function() {
+ if(this.checked) {
+ $(".otp_controls").removeClass("hidden");
+ $("#otp_length").rules("add", {required:true, min:4, max:8});
+ $("#otp_lifetime").rules("add", {required:true, min:60, max:600});
+ return true;
+ }
+ $(".otp_controls").addClass("hidden");
+ $("#otp_length").rules("remove");
+ $("#otp_lifetime").rules("remove");
+ return true;
+ });
+
+ $('#token_endpoint_auth_method').change(function() {
+ var auth_method = $(this).val();
+
+ if(auth_method === 'private_key_jwt' || auth_method === 'client_secret_jwt')
+ {
+ var signing_alg_select = $('#token_endpoint_auth_signing_alg')
+ $('#token_endpoint_auth_signing_alg_group').show();
+ signing_alg_select.empty();
+ var result = [];
+
+ if(auth_method === 'private_key_jwt')
+ {
+ result = oauth2_supported_algorithms.sig_algorihtms.rsa;
+ }
+ else
+ {
+ result = oauth2_supported_algorithms.sig_algorihtms.mac;
+ }
+
+ $.each(result, function(index, item) {
+ var key = item === 'none' ? '' : item;
+ signing_alg_select.append($(" ").val(key).text(item));
+ });
+ }
+ else
+ {
+ $('#token_endpoint_auth_signing_alg_group').hide();
+ }
+ });
+
+ $('#token_endpoint_auth_method').trigger('change');
+
+ form.submit(function(e){
+ var is_valid = $(this).valid();
+ if (is_valid) {
+ $('#save-application-security').attr('disabled','disabled');
+ var application_data = $(this).serializeForm();
+
+ $.ajax(
+ {
+ type: "PUT",
+ url: dataClientUrls.update,
+ data: JSON.stringify(application_data),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ $('#save-application-security').removeAttr('disabled');
+ displaySuccessMessage('Data saved successfully.', form);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ $('#save-application-security').removeAttr('disabled');
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ e.preventDefault();
+ return false;
+ });
+
+});
diff --git a/assets/js/oauth2/profile/edit-client-tokens.js b/assets/js/oauth2/profile/edit-client-tokens.js
new file mode 100644
index 00000000..80cf31f1
--- /dev/null
+++ b/assets/js/oauth2/profile/edit-client-tokens.js
@@ -0,0 +1,226 @@
+var pageSizeTokens = 25;
+var refreshTokenCurrentPage = 1;
+var accessTokenCurrentPage = 1;
+
+function updateAccessTokenList(page, page_size){
+ //reload access tokens
+ accessTokenCurrentPage = page;
+ $.ajax({
+ type: "GET",
+ url: TokensUrls.AccessTokenUrls.get +'?page='+page+'&per_page='+page_size,
+ dataType: "json",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ //load data...
+
+ if(page.data.length === 0){
+ $('#table-access-tokens').hide();
+ $('#info-access-tokens').show();
+ }
+ else{
+ $('#info-access-tokens').hide();
+ $('#table-access-tokens').show();
+ var template = $('Revoke ');
+ var directives = {
+ 'tr':{
+ 'token<-context':{
+ '@id' :'token.value',
+ 'td.issued' : function(arg){
+ return moment.unix(arg.item.created_at).format();
+ },
+ 'td.scope' :'token.scope',
+ 'td.lifetime':'token.remaining_lifetime',
+ 'a@href':function(arg){
+ var token_value = arg.item.value;
+ var href = TokensUrls.AccessTokenUrls.delete;
+ return href.replace('-1',token_value);
+ },
+ 'a@data-value' :'token.value'
+ }
+ }
+ };
+ var html = template.render(page.data, directives);
+ $('#body-access-tokens').html(html.html());
+ var pages_html = '';
+ for(var i = 0 ; i < page.last_page ; i++){
+ var active = ((i+1) == accessTokenCurrentPage) ? true : false;
+ pages_html += ""+(i+1)+" ";
+ }
+ $('#access_token_paginator').html(pages_html)
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+}
+
+function updateRefreshTokenList(page, page_size){
+ //reload access tokens
+ refreshTokenCurrentPage = page;
+ $.ajax({
+ type: "GET",
+ url: TokensUrls.RefreshTokenUrl.get+'?page='+page+'&per_page='+page_size,
+ dataType: "json",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ //load data...
+
+ if(page.data.length===0){
+ $('#table-refresh-tokens').hide();
+ $('#info-refresh-tokens').show();
+ }
+ else{
+ $('#info-refresh-tokens').hide();
+ $('#table-refresh-tokens').show();
+ var template = $('Revoke ');
+ var directives = {
+ 'tr':{
+ 'token<-context':{
+ '@id' :'token.value',
+ 'td.issued' : function(arg){
+ return moment.unix(arg.item.created_at).format();
+ },
+ 'td.scope' :'token.scope',
+ 'td.lifetime':function(arg){
+ var token_lifetime = arg.item.lifetime;
+ return token_lifetime===0?'Not Expire':token_lifetime;
+ },
+ 'a@href':function(arg){
+ var token_value = arg.item.value;
+ var href = TokensUrls.RefreshTokenUrl.delete;
+ return href.replace('-1',token_value);
+ },
+ 'a@data-value' :'token.value'
+ }
+ }
+ };
+ var html = template.render(page.data, directives);
+ $('#body-refresh-tokens').html(html.html());
+ var pages_html = '';
+ for(var i = 0 ; i < page.last_page ; i++){
+ var active = ((i+1) == refreshTokenCurrentPage) ? true : false;
+ pages_html += ""+(i+1)+" ";
+ }
+ $('#refresh_token_paginator').html(pages_html);
+ updateAccessTokenList(1, pageSizeTokens);
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+}
+
+jQuery(document).ready(function($){
+
+
+
+ if($('#table-access-tokens tr').length == 1){
+ $('#info-access-tokens').show();
+ $('#table-access-tokens').hide();
+ }
+ else{
+ $('#info-access-tokens').hide();
+ $('#table-access-tokens').show();
+ }
+
+ if($('#table-refresh-tokens tr').length == 1){
+ $('#info-refresh-tokens').show();
+ $('#table-refresh-tokens').hide();
+ }
+ else{
+ $('#info-refresh-tokens').hide();
+ $('#table-refresh-tokens').show();
+ }
+
+ $("body").on('click','.refresh-access-tokens',function(event){
+ updateAccessTokenList(1, pageSizeTokens);
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on('click','.refresh-refresh-tokens',function(event){
+ updateRefreshTokenList(1, pageSizeTokens);
+ event.preventDefault();
+ return false;
+ });
+
+ $("body").on("click",".access_token_page", function(event){
+ event.preventDefault();
+ accessTokenCurrentPage = $(this).data('page-nbr');
+
+ updateAccessTokenList(accessTokenCurrentPage, pageSizeTokens);
+
+ return false;
+ });
+
+ $("body").on("click",".refresh_token_page", function(event){
+ event.preventDefault();
+ refreshTokenCurrentPage= $(this).data('page-nbr');
+
+ updateRefreshTokenList(refreshTokenCurrentPage, pageSizeTokens);
+
+ return false;
+ });
+
+ $("body").on('click',".revoke-token",function(event){
+
+ var link = $(this);
+ var value = link.data('value');
+ var hint = link.data('hint');
+ var url = link.attr('href');
+ var table_id = hint ==='refresh-token'? 'table-refresh-tokens':'table-access-tokens';
+ var info_id = hint ==='refresh-token'? 'info-refresh-tokens':'info-access-tokens';
+ var confirm_msg = hint ==='refresh-token'? 'Revoking this refresh token also will become void all related Access Tokens.':'Revoke Access Token?';
+
+ swal({
+ title: "Are you sure?",
+ text: confirm_msg,
+ type: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: "Yes, revoke it!",
+ closeOnConfirm: true
+ }).then(
+ function(result){
+ if(!result) return;
+ $.ajax(
+ {
+ type: "DELETE",
+ url: url,
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ var row = $('#'+value);
+ row.remove();
+ var row_qty = $('#'+table_id+' tr').length;
+ if(row_qty===1){ //only we have the header ...
+ if(hint=='refresh-token' && refreshTokenCurrentPage > 1) {
+ refreshTokenCurrentPage -= 1;
+ updateRefreshTokenList(refreshTokenCurrentPage, pageSizeTokens);
+ }
+ if(hint=='access-token' && accessTokenCurrentPage > 1) {
+ accessTokenCurrentPage -= 1;
+ updateAccessTokenList(accessTokenCurrentPage, pageSizeTokens);
+ }
+ else{
+ $('#'+table_id).hide();
+ $('#'+info_id).show();
+ }
+ }
+ if(hint=='refresh-token'){
+ updateAccessTokenList(1, pageSizeTokens);
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+ event.preventDefault();
+ return false;
+ });
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/profile/edit-user-grants.js b/assets/js/oauth2/profile/edit-user-grants.js
new file mode 100644
index 00000000..08208ff3
--- /dev/null
+++ b/assets/js/oauth2/profile/edit-user-grants.js
@@ -0,0 +1,216 @@
+var pageSizeUserGrants = 25;
+var refreshTokenCurrentPage = 1;
+var accessTokenCurrentPage = 1;
+
+function updateAccessTokenList(page, page_size){
+ accessTokenCurrentPage = page;
+ //reload access tokens
+ $.ajax({
+ type: "GET",
+ url: TokensUrls.AccessTokenUrls.get +'?page='+page+'&per_page='+page_size,
+ dataType: "json",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ //load data...
+
+ if(page.data.length === 0){
+ $('#table-access-tokens').hide();
+ $('#info-access-tokens').show();
+ }
+ else{
+ $('#info-access-tokens').hide();
+ $('#table-access-tokens').show();
+ var template = $('Revoke ');
+ var directives = {
+ 'tr':{
+ 'token<-context':{
+ '@id' :'token.value',
+ 'td.app_type' :'token.client_type',
+ 'td.app_name' :'token.client_name',
+ 'td.issued' :'token.created_at',
+ 'td.scope' :'token.scope',
+ 'td.lifetime' :'token.remaining_lifetime',
+ 'a@href':function(arg){
+ var token_value = arg.item.value;
+ var href = TokensUrls.AccessTokenUrls.delete;
+ return href.replace('-1',token_value);
+ },
+ 'a@data-value' :'token.value'
+ }
+ }
+ };
+ var html = template.render(page.data, directives);
+ $('#body-access-tokens').html(html.html());
+ var pages_html = '';
+ var pages_qty = Math.ceil(page.total / page.per_page);
+ for(var i = 0 ; i < pages_qty ; i++){
+ var active = ((i+1) == accessTokenCurrentPage) ? true : false;
+ pages_html += ""+(i+1)+" ";
+ }
+ $('#access_token_paginator').html(pages_html)
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+}
+
+function updateRefreshTokenList(page, page_size){
+ refreshTokenCurrentPage = page;
+ //reload access tokens
+ $.ajax({
+ type: "GET",
+ url: TokensUrls.RefreshTokenUrl.get+'?page='+page+'&per_page='+page_size,
+ dataType: "json",
+ timeout:60000,
+ success: function (page,textStatus,jqXHR) {
+ //load data...
+
+ if(page.data.length===0){
+ $('#table-refresh-tokens').hide();
+ $('#info-refresh-tokens').show();
+ }
+ else{
+ $('#info-refresh-tokens').hide();
+ $('#table-refresh-tokens').show();
+ var template = $('Revoke ');
+ var directives = {
+ 'tr':{
+ 'token<-context':{
+ '@id' :'token.value',
+ 'td.app_type' :'token.client_type',
+ 'td.app_type' :'token.client_name',
+ 'td.issued' :'token.created_at',
+ 'td.scope' :'token.scope',
+ 'td.lifetime' : function(arg){
+ var token_lifetime = arg.item.remaining_lifetime;
+ return token_lifetime===0?'Not Expire':token_lifetime;
+ },
+ 'a@href':function(arg){
+ var token_value = arg.item.value;
+ var href = TokensUrls.RefreshTokenUrl.delete;
+ return href.replace('-1',token_value);
+ },
+ 'a@data-value' :'token.value'
+ }
+ }
+ };
+ var html = template.render(page.data, directives);
+ $('#body-refresh-tokens').html(html.html());
+ var pages_html = '';
+ var pages_qty = Math.ceil(page.total / page.per_page);
+ for(var i = 0 ; i < pages_qty ; i++){
+ var active = ((i+1) == refreshTokenCurrentPage) ? true : false;
+ pages_html += ""+(i+1)+" ";
+ }
+ $('#refresh_token_paginator').html(pages_html)
+ updateAccessTokenList(1, pageSizeUserGrants);
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ });
+}
+
+jQuery(document).ready(function($){
+
+
+
+ $('#oauth2-console','#main-menu').addClass('active');
+
+ if($('#table-access-tokens tr').length===1){
+ $('#info-access-tokens').show();
+ $('#table-access-tokens').hide();
+ }
+ else{
+ $('#info-access-tokens').hide();
+ $('#table-access-tokens').show();
+ }
+
+ if($('#table-refresh-tokens tr').length===1){
+ $('#info-refresh-tokens').show();
+ $('#table-refresh-tokens').hide();
+ }
+ else{
+ $('#info-refresh-tokens').hide();
+ $('#table-refresh-tokens').show();
+ }
+
+ $("body").on("click",".access_token_page", function(event){
+ event.preventDefault();
+ accessTokenCurrentPage = $(this).data('page-nbr');
+ updateAccessTokenList(accessTokenCurrentPage, pageSizeUserGrants);
+ return false;
+ });
+
+ $("body").on("click",".refresh_token_page", function(event){
+ event.preventDefault();
+ refreshTokenCurrentPage = $(this).data('page-nbr');
+ updateRefreshTokenList(refreshTokenCurrentPage, pageSizeUserGrants);
+ return false;
+ });
+
+ $("body").on('click',".revoke-token",function(event){
+
+ var link = $(this);
+ var value = link.data('value');
+ var hint = link.data('hint');
+ var url = link.attr('href');
+ var table_id = hint ==='refresh-token'? 'table-refresh-tokens':'table-access-tokens';
+ var info_id = hint ==='refresh-token'? 'info-refresh-tokens':'info-access-tokens';
+ var confirm_msg = hint ==='refresh-token'? 'Revoking this refresh token also will become void all related Access Tokens':'Revoke Access Token ?';
+
+ swal({
+ title: "Are you sure?",
+ text: confirm_msg,
+ type: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: "Yes, revoke it!",
+ closeOnConfirm: true
+ }).then(
+ function(result){
+ if(!result) return;
+ $.ajax(
+ {
+ type: "DELETE",
+ url: url,
+ dataType: "json",
+ timeout:60000,
+ success: function (data,textStatus,jqXHR) {
+ //load data...
+ var row = $('#'+value);
+ row.remove();
+ var row_qty = $('#'+table_id+' tr').length;
+ if(row_qty===1){ //only we have the header ...
+
+ if(hint=='refresh-token' && refreshTokenCurrentPage > 1) {
+ refreshTokenCurrentPage -= 1;
+ updateRefreshTokenList(refreshTokenCurrentPage, pageSizeUserGrants);
+ }
+ if(hint=='access-token' && accessTokenCurrentPage > 1) {
+ accessTokenCurrentPage -= 1;
+ updateAccessTokenList(accessTokenCurrentPage, pageSizeUserGrants);
+ }
+ else{
+ $('#'+table_id).hide();
+ $('#'+info_id).show();
+ }
+ }
+ if(hint=='refresh-token'){
+ updateAccessTokenList(1, pageSizeUserGrants);
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ });
+
+ event.preventDefault();
+ return false;
+ });
+});
\ No newline at end of file
diff --git a/assets/js/oauth2/session/check.session.js b/assets/js/oauth2/session/check.session.js
new file mode 100644
index 00000000..1629d335
--- /dev/null
+++ b/assets/js/oauth2/session/check.session.js
@@ -0,0 +1,108 @@
+(function( $ ){
+
+ /**
+ * @param string message
+ * @returns string
+ */
+ function hash(message)
+ {
+ var hash = CryptoJS.SHA256(message).toString();
+ console.log('calculated hash '+hash+' from message '+message);
+ return hash;
+ }
+
+ /**
+ *
+ * @param string clientId
+ * @param string origin
+ * @param string opbs
+ * @param string salt
+ * @returns string
+ */
+ function computeSessionStateHash(clientId, origin, opbs, salt)
+ {
+ return hash(clientId + origin + opbs + salt);
+ }
+
+ /**
+ *
+ * @param origin
+ * @param message
+ * @returns string
+ */
+ function calculateSessionStateResult(origin, message) {
+ try
+ {
+
+ if (!origin || !message)
+ {
+ console.log("IDP::calculateSessionStateResult !origin || !message. return error");
+ return "error";
+ }
+
+ var messageParts = message.split(' ');
+ if (messageParts.length !== 2)
+ {
+ return "error";
+ }
+
+ var clientId = messageParts[0];
+ var sessionState = messageParts[1];
+
+ if (!clientId || !sessionState)
+ {
+ console.log("IDP::calculateSessionStateResult !clientId || !sessionState. return error");
+ return "error";
+ }
+
+ var sessionStateParts = sessionState.split('.');
+ if (sessionStateParts.length !== 2)
+ {
+ console.log("IDP::calculateSessionStateResult sessionStateParts.length !== 2. return error");
+ return "error";
+ }
+
+ var clientHash = sessionStateParts[0];
+ var salt = sessionStateParts[1];
+ //console.log("clientHash "+clientHash);
+ //console.log("salt "+salt);
+
+ if (!clientHash || !salt)
+ {
+ console.log("IDP::calculateSessionStateResult missing clientHash or salt. return error");
+ return "error";
+ }
+
+ var opbs = $.cookie('op_bs');
+ // posible cookies not enabled or third party cookies not enabled
+ if (opbs == "undefined" || typeof(opbs) == "undefined") {
+ console.log("IDP::calculateSessionStateResult missing op_bs cookie. return error");
+ return "error";
+ }
+ console.log("IDP::calculateSessionStateResult opbs " + opbs)
+ var expectedHash = computeSessionStateHash(clientId, origin, opbs, salt);
+ var res = clientHash === expectedHash ? "unchanged" : "changed";
+ console.log("IDP::calculateSessionStateResult res "+ res);
+ return res;
+ }
+ catch(e)
+ {
+ console.log("IDP::calculateSessionStateResult exception "+ e);
+ return "error";
+ }
+ }
+
+ if (window.parent !== window)
+ {
+ window.addEventListener("message", function (e)
+ {
+ if(e.origin == window.origin){
+ return;
+ }
+ var result = calculateSessionStateResult(e.origin, e.data);
+ e.source.postMessage(result, e.origin);
+ }, false);
+ }
+
+
+}( jQuery ));
\ No newline at end of file
diff --git a/assets/js/openid/consent.js b/assets/js/openid/consent.js
new file mode 100644
index 00000000..582e336b
--- /dev/null
+++ b/assets/js/openid/consent.js
@@ -0,0 +1,15 @@
+jQuery(document).ready(function($){
+
+ $("body").on('click',"#cancel_authorization",function(event){
+ var $form = $('#authorization_form');
+ $("#deny_once").prop("checked", true)
+ $form.submit();
+ event.preventDefault();
+ return false;
+ });
+
+ $('#authorization_form').submit(function(){
+ $('.btn-consent-action').attr('disabled', 'disabled');
+ return true;
+ })
+});
\ No newline at end of file
diff --git a/assets/js/profile.js b/assets/js/profile.js
new file mode 100644
index 00000000..4c68ce53
--- /dev/null
+++ b/assets/js/profile.js
@@ -0,0 +1,206 @@
+(function ($) {
+
+ $('body').ajax_loader();
+
+ $(document).ready(function ($) {
+
+
+ var options = {};
+
+ options.ui =
+ {
+ showPopover: false,
+ showErrors: true,
+ showProgressBar: true,
+ showVerdictsInsideProgressBar: true,
+ };
+
+ options.rules =
+ {
+ activated: {
+ wordTwoCharacterClasses: true,
+ wordRepetitions: true
+ }
+ };
+
+ $(':password').pwstrength(options);
+
+ var form = $('#user-form');
+
+ var validator = form.validate({
+ rules: {
+ "first_name": { required: true},
+ "last_name": { required: true},
+ "identifier": { required: true},
+ "email": { required: true, custom_email: true},
+ "second_email": { custom_email: true},
+ "third_email": { custom_email: true},
+ }
+ });
+
+ //$('#bio').summernote();
+ //$('#statement_of_interest').summernote();
+
+ var simplemdeBio = new SimpleMDE({element: $("#bio")[0]});
+ var simplemdeStatement = new SimpleMDE({element: $("#statement_of_interest")[0]});
+
+ $('#birthday').datepicker();
+
+ $('#img-pic', form).data('original-src', $('#img-pic', form).attr('src'));
+
+ $('#country_iso_code').chosen({width: '100%', height: '34px'});
+ $("#country_iso_code").val(current_country);
+ $("#country_iso_code").trigger("chosen:updated");
+
+ $('#country_iso_code', form).change(function () {
+ validator.resetForm();
+ });
+
+ $('#language').chosen({width: '100%', height: '34px'});
+ $('#language', form).change(function () {
+ validator.resetForm();
+ });
+
+ $("#language").val(current_language);
+ $("#language").trigger("chosen:updated");
+
+ if (current_gender != '') {
+ $("#gender").val(current_gender);
+ if (current_gender == 'Specify') {
+ $('#gender_specify').removeClass('hide').fadeIn();
+ } else {
+ $('#gender_specify').fadeOut();
+ $('#gender_specify').val('');
+ }
+ }
+
+ $('#gender', form).change(function () {
+ var value = $(this).val();
+ if (value == 'Specify') {
+ $('#gender_specify').removeClass('hide').fadeIn();
+ } else {
+ $('#gender_specify').fadeOut();
+ $('#gender_specify').val('');
+ }
+ });
+
+ form.submit(function (event) {
+ var is_valid = validator.valid();
+ if (is_valid) {
+ $('body').ajax_loader();
+ validator.resetForm();
+ var user = form.serializeForm();
+ var birthday = user.birthday;
+ delete user.birthday;
+ if (typeof birthday != "undefined" && birthday != '') {
+ user.birthday = moment(birthday).unix();
+ } else {
+ user.birthday = '';
+ }
+
+ // get values
+ user.bio = simplemdeBio.value();
+ user.statement_of_interest = simplemdeStatement.value();
+
+ var href = $(this).attr('action');
+ var data = new FormData();
+
+ data.append('user', JSON.stringify(user));
+
+ if ($('#pic', form)[0].files.length > 0)
+ data.append('pic', $('#pic', form)[0].files[0]);
+
+ $.ajax(
+ {
+ type: "PUT",
+ url: href,
+ data: data,
+ cache: false,
+ contentType: false,
+ processData: false,
+ timeout: 60000,
+ success: function (data, textStatus, jqXHR) {
+ $('body').ajax_loader('stop');
+ swal({
+ title: "Success!",
+ type: "success",
+ text: "User info updated successfully!",
+ });
+ // reset password form
+ $("#password_container").hide();
+ $("#current_password").val('');
+ $("#password").val('');
+ $("#current_password").val('');
+ $('.change-password-link').show();
+ location.reload(true);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ $('body').ajax_loader('stop');
+ ajaxError(jqXHR, textStatus, errorThrown);
+ }
+ }
+ );
+ }
+ event.preventDefault();
+ return false;
+ });
+
+ function readURL(input) {
+ if (input.files && input.files[0]) {
+ var reader = new FileReader();
+
+ reader.onload = function (e) {
+ $('#img-pic').attr('src', e.target.result);
+ }
+ reader.readAsDataURL(input.files[0]);
+ }
+ }
+
+ function fileValidation(input) {
+
+ var filePath = input.value;
+
+ // Allowing file type
+ var allowedExtensions =
+ /(\.png|\.jpeg|\.jpg)$/i;
+
+ if (!allowedExtensions.exec(filePath)) {
+ swal({
+ title: "Validation Error",
+ type: "warning",
+ text: "Invalid file type",
+ });
+ var imgSrc = $('#img-pic', form).data('original-src');
+ $(input).val('');
+ $('#img-pic', form).attr('src', imgSrc);
+ return false;
+ }
+ return true;
+ }
+
+ $("#pic", form).change(function (evt) {
+ if (fileValidation(this)) {
+ readURL(this);
+ return true;
+ }
+ evt.preventDefault();
+ return false;
+ });
+
+ $("#password_container").hide();
+
+ $("body").on("click", ".change-password-link", function (event) {
+ $(this).hide();
+ $("#password_container").show();
+ event.preventDefault();
+ return false;
+ });
+
+ $('#sidebar').removeClass('hide');
+
+ $('body').ajax_loader('stop');
+ });
+
+
+// End of closure.
+}(jQuery));
\ No newline at end of file
diff --git a/assets/js/urlfragment.jquery.js b/assets/js/urlfragment.jquery.js
new file mode 100644
index 00000000..a8756d90
--- /dev/null
+++ b/assets/js/urlfragment.jquery.js
@@ -0,0 +1,95 @@
+(function( $ ){
+
+ var original_hash = '';
+ var hash = {};
+
+ var methods = {
+ init : function(options) {
+ // This is the easiest way to have default options.
+ var settings = $.extend({
+ // These are the defaults.
+ original_hash: window.location.hash
+ }, options );
+
+ hash = convertToHash(settings.original_hash);
+ },
+ getParam: function(key)
+ {
+ if(original_hash !== window.location.hash){
+ original_hash = window.location.hash;
+ hash = convertToHash(original_hash);
+ }
+
+ if(!hash.hasOwnProperty(key) ) return null;
+ return hash[key];
+ },
+ getParams: function(){
+ if(original_hash !== window.location.hash){
+ original_hash = window.location.hash;
+ hash = convertToHash(original_hash);
+ }
+
+ return hash;
+ },
+ setParam: function (key, value)
+ {
+ if(original_hash !== window.location.hash){
+ original_hash = window.location.hash;
+ hash = convertToHash(original_hash);
+ }
+ if(value !== null && value !== '')
+ hash[key] = value;
+ else
+ delete hash[key];
+ return this;
+ },
+ serialize:function(){
+ var res = '';
+ for(var key in hash)
+ {
+ var val = hash[key];
+ if(res !== '') res += '&';
+ res += key+'='+val;
+ }
+ return res;
+ },
+ clear:function(){
+ window.location.hash = '';
+ original_hash = '';
+ hash = {};
+ }
+ };
+
+ //private methods
+
+ function convertToHash(str_hash)
+ {
+ str_hash = str_hash.substr(1).toLowerCase();
+ var params = str_hash.split('&');
+ var res = {};
+ for(var param of params)
+ {
+ param = param.split('=');
+ if(param.length !== 2) continue;
+ var val = param[1].trim();
+ if(val === '') continue;
+ res[param[0]] = param[1];
+ }
+ return res;
+ }
+
+ $.fn.url_fragment = function(methodOrOptions) {
+ if ( methods[methodOrOptions] ) {
+ return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+ } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
+ // Default to "init"
+ return methods.init.apply( this, arguments );
+ } else {
+ $.error( 'Method ' + method + ' does not exist on jQuery.url_fragment' );
+ }
+ };
+
+ //helper functions
+
+// End of closure.
+}( jQuery));
\ No newline at end of file
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 00000000..e69de29b
diff --git a/favicon/android-chrome-192x192.png b/favicon/android-chrome-192x192.png
new file mode 100644
index 00000000..f8e9b4a4
Binary files /dev/null and b/favicon/android-chrome-192x192.png differ
diff --git a/favicon/android-chrome-256x256.png b/favicon/android-chrome-256x256.png
new file mode 100644
index 00000000..be61d703
Binary files /dev/null and b/favicon/android-chrome-256x256.png differ
diff --git a/favicon/apple-touch-icon.png b/favicon/apple-touch-icon.png
new file mode 100644
index 00000000..a9128f75
Binary files /dev/null and b/favicon/apple-touch-icon.png differ
diff --git a/favicon/browserconfig.xml b/favicon/browserconfig.xml
new file mode 100644
index 00000000..74bb89ac
--- /dev/null
+++ b/favicon/browserconfig.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ #da532c
+
+
+
diff --git a/favicon/favicon-16x16.png b/favicon/favicon-16x16.png
new file mode 100644
index 00000000..16c8e9e3
Binary files /dev/null and b/favicon/favicon-16x16.png differ
diff --git a/favicon/favicon-32x32.png b/favicon/favicon-32x32.png
new file mode 100644
index 00000000..2ea69cac
Binary files /dev/null and b/favicon/favicon-32x32.png differ
diff --git a/favicon/favicon.ico b/favicon/favicon.ico
new file mode 100644
index 00000000..4cb53bb4
Binary files /dev/null and b/favicon/favicon.ico differ
diff --git a/favicon/manifest.json b/favicon/manifest.json
new file mode 100644
index 00000000..1bf07162
--- /dev/null
+++ b/favicon/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "",
+ "icons": [
+ {
+ "src": "\/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image\/png"
+ },
+ {
+ "src": "\/android-chrome-256x256.png",
+ "sizes": "256x256",
+ "type": "image\/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/favicon/mstile-150x150.png b/favicon/mstile-150x150.png
new file mode 100644
index 00000000..c012089f
Binary files /dev/null and b/favicon/mstile-150x150.png differ
diff --git a/favicon/safari-pinned-tab.svg b/favicon/safari-pinned-tab.svg
new file mode 100644
index 00000000..702fcf89
--- /dev/null
+++ b/favicon/safari-pinned-tab.svg
@@ -0,0 +1,27 @@
+
+
+
+
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+
+
+
+
+
+
+
+
diff --git a/index.php b/index.php
new file mode 100644
index 00000000..c5820533
--- /dev/null
+++ b/index.php
@@ -0,0 +1,58 @@
+
+ */
+
+/*
+|--------------------------------------------------------------------------
+| Register The Auto Loader
+|--------------------------------------------------------------------------
+|
+| Composer provides a convenient, automatically generated class loader for
+| our application. We just need to utilize it! We'll simply require it
+| into the script here so that we don't have to worry about manual
+| loading any of our classes later on. It feels nice to relax.
+|
+*/
+
+require __DIR__.'/../bootstrap/autoload.php';
+
+/*
+|--------------------------------------------------------------------------
+| Turn On The Lights
+|--------------------------------------------------------------------------
+|
+| We need to illuminate PHP development, so let us turn on the lights.
+| This bootstraps the framework and gets it ready for use, then it
+| will load up this application so that we can run it and send
+| the responses back to the browser and delight our users.
+|
+*/
+
+$app = require_once __DIR__.'/../bootstrap/app.php';
+
+/*
+|--------------------------------------------------------------------------
+| Run The Application
+|--------------------------------------------------------------------------
+|
+| Once we have the application, we can handle the incoming request
+| through the kernel, and send the associated response back to
+| the client's browser allowing them to enjoy the creative
+| and wonderful application we have prepared for them.
+|
+*/
+
+$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
+
+$response = $kernel->handle(
+ $request = Illuminate\Http\Request::capture()
+);
+
+$response->send();
+
+$kernel->terminate($request, $response);
diff --git a/openapi/pr-106/api-docs.json b/openapi/pr-106/api-docs.json
new file mode 100644
index 00000000..08528d94
--- /dev/null
+++ b/openapi/pr-106/api-docs.json
@@ -0,0 +1,1450 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-106/index.html b/openapi/pr-106/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-106/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-108/api-docs.json b/openapi/pr-108/api-docs.json
new file mode 100644
index 00000000..f55d3860
--- /dev/null
+++ b/openapi/pr-108/api-docs.json
@@ -0,0 +1,1802 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-108/index.html b/openapi/pr-108/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-108/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-109/api-docs.json b/openapi/pr-109/api-docs.json
new file mode 100644
index 00000000..cfd59670
--- /dev/null
+++ b/openapi/pr-109/api-docs.json
@@ -0,0 +1,546 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-109/index.html b/openapi/pr-109/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-109/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-110/api-docs.json b/openapi/pr-110/api-docs.json
new file mode 100644
index 00000000..fd3c4bbf
--- /dev/null
+++ b/openapi/pr-110/api-docs.json
@@ -0,0 +1,519 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-110/index.html b/openapi/pr-110/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-110/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-111/api-docs.json b/openapi/pr-111/api-docs.json
new file mode 100644
index 00000000..64d96fa3
--- /dev/null
+++ b/openapi/pr-111/api-docs.json
@@ -0,0 +1,501 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-111/index.html b/openapi/pr-111/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-111/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-112/api-docs.json b/openapi/pr-112/api-docs.json
new file mode 100644
index 00000000..a8c18d4c
--- /dev/null
+++ b/openapi/pr-112/api-docs.json
@@ -0,0 +1,487 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-112/index.html b/openapi/pr-112/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-112/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-113/api-docs.json b/openapi/pr-113/api-docs.json
new file mode 100644
index 00000000..e20199cc
--- /dev/null
+++ b/openapi/pr-113/api-docs.json
@@ -0,0 +1,3311 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/oauth2/auth": {
+ "get": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OAuth2 Authorization Endpoint (GET)",
+ "description": "Initiates an OAuth2 authorization flow. Supports Authorization Code, Implicit, Hybrid, and OpenID Connect flows. Per RFC 6749 §3.1, GET is required.",
+ "operationId": "oauth2AuthorizeGet",
+ "parameters": [
+ {
+ "name": "response_type",
+ "in": "query",
+ "description": "The OAuth 2.0 specification allows for registration of space-separated response_type parameter values. If a Response Type contains one of more space characters (%20), it is compared as a space-delimited list of values in which the order of values does not matter. Possible values are: code, token, id_token, otp, none. The \"none\" value cannot be used with any other response type value.",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "client_id",
+ "in": "query",
+ "description": "OAuth2 client identifier",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "redirect_uri",
+ "in": "query",
+ "description": "Redirect URI",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ {
+ "name": "scope",
+ "in": "query",
+ "description": "Space-delimited scopes",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "state",
+ "in": "query",
+ "description": "Opaque state parameter",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "approval_prompt",
+ "in": "query",
+ "description": "Indicates whether the user should be re-prompted for consent. The default is auto, so a given user should only see the consent page for a given set of scopes the first time through the sequence. If the value is force, then the user sees a consent page even if they previously gave consent to your application for a given set of scopes.",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "force"
+ ]
+ }
+ },
+ {
+ "name": "access_type",
+ "in": "query",
+ "description": "Indicates whether your application needs to access an API when the user is not present at the browser. This parameter defaults to online. If your application needs to refresh access tokens when the user is not present at the browser, then use offline. This will result in your application obtaining a refresh token the first time your application exchanges an authorization code for a user.",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "online",
+ "offline"
+ ]
+ }
+ },
+ {
+ "name": "response_mode",
+ "in": "query",
+ "description": "OPTIONAL. Informs the Authorization Server of the mechanism to be used for returning Authorization Response parameters from the Authorization Endpoint. This use of this parameter is NOT RECOMMENDED with a value that specifies the same Response Mode as the default Response Mode for the Response Type used.\\nThe default Response Mode for the OAuth 2.0 code Response Type is the query encoding. For purposes of this specification, the default Response Mode for the OAuth 2.0 token Response Type is the fragment encoding.",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "query",
+ "fragment",
+ "form_post",
+ "direct"
+ ]
+ }
+ },
+ {
+ "name": "code_challenge",
+ "in": "query",
+ "description": "PKCE code challenge",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "code_challenge_method",
+ "in": "query",
+ "description": "Optional. PKCE challenge method",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "plain",
+ "S256"
+ ]
+ }
+ },
+ {
+ "name": "display",
+ "in": "query",
+ "description": "UI display preference (OIDC)",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "page",
+ "popup",
+ "touch",
+ "wap",
+ "native"
+ ]
+ }
+ },
+ {
+ "name": "tenant",
+ "in": "query",
+ "description": "Tenant identifier",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Authorization request processed (response in body), depends on \"response_mode\" param"
+ },
+ "302": {
+ "description": "Redirect to client redirect_uri with authorization code, tokens, or error"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OAuth2 Authorization Endpoint (POST)",
+ "description": "Initiates an OAuth2 authorization flow via POST. Same parameters as GET but sent as form data.",
+ "operationId": "oauth2AuthorizePost",
+ "requestBody": {
+ "description": "Authorization request parameters",
+ "required": true,
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2AuthorizationRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Authorization request processed (response in body), depends on \"response_mode\" param"
+ },
+ "302": {
+ "description": "Redirect to client redirect_uri with authorization code, tokens, or error"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/oauth2/token": {
+ "post": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OAuth2 Token Endpoint",
+ "description": "Issues access tokens. Supports authorization_code, client_credentials, password, refresh_token, and passwordless grant types.",
+ "operationId": "oauth2Token",
+ "requestBody": {
+ "description": "Token request parameters",
+ "required": true,
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2TokenRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful token response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2TokenResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Token error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2ProviderClientBasic": []
+ },
+ {
+ "OAuth2ProviderSecurity": []
+ }
+ ]
+ }
+ },
+ "/oauth2/token/revoke": {
+ "post": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OAuth2 Token Revocation Endpoint",
+ "description": "Revokes an access token or refresh token per RFC 7009. The endpoint is idempotent — revoking a non-existent token returns success.",
+ "operationId": "oauth2RevokeToken",
+ "requestBody": {
+ "description": "Token revocation parameters",
+ "required": true,
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2TokenRevocationRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Token successfully revoked"
+ },
+ "400": {
+ "description": "Revocation error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2ProviderClientBasic": []
+ },
+ {
+ "OAuth2ProviderSecurity": []
+ }
+ ]
+ }
+ },
+ "/oauth2/token/introspection": {
+ "post": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OAuth2 Token Introspection Endpoint",
+ "description": "Validates and returns metadata about an access token per RFC 7662. Returns detailed information about the token including associated user data.",
+ "operationId": "oauth2IntrospectToken",
+ "requestBody": {
+ "description": "Token introspection parameters",
+ "required": true,
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2TokenIntrospectionRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Token introspection response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2IntrospectionResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Introspection error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2ProviderClientBasic": []
+ },
+ {
+ "OAuth2ProviderSecurity": []
+ }
+ ]
+ }
+ },
+ "/oauth2/certs": {
+ "get": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "JSON Web Key Set (JWKS) Endpoint",
+ "description": "Returns the OpenID Provider JSON Web Key Set document containing public keys used for signing tokens (RFC 7517).",
+ "operationId": "oauth2GetCerts",
+ "responses": {
+ "200": {
+ "description": "JWKS document",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/JWKSResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/.well-known/openid-configuration": {
+ "get": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OpenID Connect Discovery Endpoint",
+ "description": "Returns the OpenID Provider Configuration document per OpenID Connect Discovery 1.0.",
+ "operationId": "oauth2Discovery",
+ "responses": {
+ "200": {
+ "description": "OpenID Connect Discovery document",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OpenIDDiscoveryResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/oauth2/.well-known/openid-configuration": {
+ "get": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OpenID Connect Discovery Endpoint (oauth2-prefixed alias)",
+ "description": "Alias for /.well-known/openid-configuration. Returns the OpenID Provider Configuration document per OpenID Connect Discovery 1.0.",
+ "operationId": "oauth2DiscoveryAlias",
+ "responses": {
+ "200": {
+ "description": "OpenID Connect Discovery document",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OpenIDDiscoveryResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/oauth2/check-session": {
+ "get": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OpenID Connect Check Session iFrame",
+ "description": "Returns the HTML iFrame page used by clients for OIDC Session Management (OpenID Connect Session Management 1.0 §3). The URL is advertised as check_session_iframe in the discovery document.",
+ "operationId": "oauth2CheckSession",
+ "responses": {
+ "200": {
+ "description": "Session check iFrame HTML page",
+ "content": {
+ "text/html": {}
+ }
+ }
+ }
+ }
+ },
+ "/oauth2/end-session": {
+ "get": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OpenID Connect End Session Endpoint (GET)",
+ "description": "Initiates RP-Initiated Logout per OpenID Connect Session Management 1.0. Terminates the user session and optionally redirects to the post-logout URI.",
+ "operationId": "oauth2EndSessionGet",
+ "parameters": [
+ {
+ "name": "client_id",
+ "in": "query",
+ "description": "OAuth2 client identifier",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "id_token_hint",
+ "in": "query",
+ "description": "Previously issued ID token",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "post_logout_redirect_uri",
+ "in": "query",
+ "description": "URI to redirect after logout (must be registered)",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ {
+ "name": "state",
+ "in": "query",
+ "description": "Opaque state parameter returned in the redirect",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "302": {
+ "description": "Redirect to post_logout_redirect_uri"
+ },
+ "200": {
+ "description": "Session ended page (HTML)"
+ },
+ "400": {
+ "description": "Invalid logout request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "OAuth2 / OpenID Connect"
+ ],
+ "summary": "OpenID Connect End Session Endpoint (POST)",
+ "description": "Initiates RP-Initiated Logout via POST. Same parameters as GET but sent as form data.",
+ "operationId": "oauth2EndSessionPost",
+ "requestBody": {
+ "description": "End session parameters",
+ "required": true,
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2EndSessionRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "302": {
+ "description": "Redirect to post_logout_redirect_uri"
+ },
+ "200": {
+ "description": "Session ended page (HTML)"
+ },
+ "400": {
+ "description": "Invalid logout request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OAuth2ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "OAuth2TokenResponse": {
+ "title": "OAuth2 Token Response",
+ "description": "Successful token response per RFC 6749 §5.1",
+ "required": [
+ "access_token",
+ "token_type"
+ ],
+ "properties": {
+ "access_token": {
+ "description": "The access token issued by the authorization server",
+ "type": "string"
+ },
+ "token_type": {
+ "description": "The type of the token (typically Bearer)",
+ "type": "string",
+ "example": "Bearer"
+ },
+ "expires_in": {
+ "description": "Lifetime of the access token in seconds",
+ "type": "integer",
+ "example": 3600
+ },
+ "refresh_token": {
+ "description": "Refresh token (only if access_type=offline)",
+ "type": "string",
+ "nullable": true
+ },
+ "scope": {
+ "description": "Space-delimited list of granted scopes",
+ "type": "string"
+ },
+ "id_token": {
+ "description": "ID token JWT (only for OpenID Connect flows with openid scope)",
+ "type": "string",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "OAuth2ErrorResponse": {
+ "title": "OAuth2 Error Response",
+ "description": "Error response per RFC 6749 §5.2",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "description": "Error code",
+ "type": "string",
+ "example": "invalid_request"
+ },
+ "error_description": {
+ "description": "Human-readable error description",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "OAuth2IntrospectionResponse": {
+ "title": "OAuth2 Token Introspection Response",
+ "description": "Token introspection response per RFC 7662",
+ "required": [
+ "active"
+ ],
+ "properties": {
+ "active": {
+ "description": "Whether the token is active",
+ "type": "boolean"
+ },
+ "access_token": {
+ "description": "The access token value",
+ "type": "string"
+ },
+ "client_id": {
+ "description": "Client identifier",
+ "type": "string"
+ },
+ "application_type": {
+ "description": "Client application type",
+ "type": "string",
+ "enum": [
+ "WEB",
+ "NATIVE",
+ "JS"
+ ]
+ },
+ "token_type": {
+ "description": "Token type",
+ "type": "string",
+ "example": "Bearer"
+ },
+ "scope": {
+ "description": "Space-delimited scopes",
+ "type": "string"
+ },
+ "audience": {
+ "description": "Token audience",
+ "type": "string"
+ },
+ "expires_in": {
+ "description": "Remaining lifetime in seconds",
+ "type": "integer"
+ },
+ "user_id": {
+ "description": "Resource owner user ID",
+ "type": "integer",
+ "nullable": true
+ },
+ "user_identifier": {
+ "description": "Resource owner identifier",
+ "type": "string",
+ "nullable": true
+ },
+ "user_email": {
+ "description": "Resource owner email",
+ "type": "string",
+ "nullable": true
+ },
+ "user_first_name": {
+ "description": "Resource owner first name",
+ "type": "string",
+ "nullable": true
+ },
+ "user_last_name": {
+ "description": "Resource owner last name",
+ "type": "string",
+ "nullable": true
+ },
+ "user_pic": {
+ "description": "Resource owner profile picture URL",
+ "type": "string",
+ "format": "uri",
+ "nullable": true
+ },
+ "user_groups": {
+ "description": "Resource owner group memberships",
+ "type": "array",
+ "items": {
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "nullable": true
+ },
+ "user_email_verified": {
+ "description": "Whether the user email is verified",
+ "type": "boolean",
+ "nullable": true
+ },
+ "user_language": {
+ "description": "User preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "user_country": {
+ "description": "User country",
+ "type": "string",
+ "nullable": true
+ },
+ "allowed_return_uris": {
+ "description": "Space-delimited allowed return URIs",
+ "type": "string"
+ },
+ "allowed_origins": {
+ "description": "Space-delimited allowed origins",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "JWKSResponse": {
+ "title": "JSON Web Key Set",
+ "description": "JWK Set document per RFC 7517",
+ "required": [
+ "keys"
+ ],
+ "properties": {
+ "keys": {
+ "description": "Array of JSON Web Keys",
+ "type": "array",
+ "items": {
+ "properties": {
+ "kty": {
+ "description": "Key type",
+ "type": "string",
+ "example": "RSA"
+ },
+ "kid": {
+ "description": "Key ID",
+ "type": "string"
+ },
+ "use": {
+ "description": "Key usage",
+ "type": "string",
+ "example": "sig"
+ },
+ "alg": {
+ "description": "Algorithm",
+ "type": "string",
+ "example": "RS256"
+ },
+ "n": {
+ "description": "RSA modulus (Base64urlUInt-encoded)",
+ "type": "string"
+ },
+ "e": {
+ "description": "RSA exponent (Base64urlUInt-encoded)",
+ "type": "string",
+ "example": "AQAB"
+ }
+ },
+ "type": "object"
+ }
+ }
+ },
+ "type": "object"
+ },
+ "OpenIDDiscoveryResponse": {
+ "title": "OpenID Connect Discovery Document",
+ "description": "OpenID Provider Configuration per OpenID Connect Discovery 1.0",
+ "required": [
+ "issuer",
+ "authorization_endpoint",
+ "token_endpoint",
+ "jwks_uri",
+ "response_types_supported",
+ "subject_types_supported",
+ "id_token_signing_alg_values_supported"
+ ],
+ "properties": {
+ "issuer": {
+ "description": "Issuer identifier URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "authorization_endpoint": {
+ "description": "Authorization endpoint URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token_endpoint": {
+ "description": "Token endpoint URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "userinfo_endpoint": {
+ "description": "UserInfo endpoint URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "revocation_endpoint": {
+ "description": "Token revocation endpoint URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "introspection_endpoint": {
+ "description": "Token introspection endpoint URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "jwks_uri": {
+ "description": "JSON Web Key Set URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "scopes_supported": {
+ "description": "Supported OAuth2 scopes",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "response_types_supported": {
+ "description": "Supported response types",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "response_modes_supported": {
+ "description": "Supported response modes",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "grant_types_supported": {
+ "description": "Supported grant types",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "subject_types_supported": {
+ "description": "Supported subject types",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "id_token_signing_alg_values_supported": {
+ "description": "Supported ID token signing algorithms",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "code_challenge_methods_supported": {
+ "description": "Supported PKCE code challenge methods",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "type": "object"
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "OAuth2AuthorizationRequest": {
+ "title": "OAuth2 Authorization Request",
+ "description": "Authorization request parameters per RFC 6749 §4.1.1 and OpenID Connect Core §3.1.2.1",
+ "required": [
+ "response_type",
+ "client_id",
+ "redirect_uri"
+ ],
+ "properties": {
+ "response_type": {
+ "description": "The OAuth 2.0 specification allows for registration of space-separated response_type parameter values. If a Response Type contains one of more space characters (%20), it is compared as a space-delimited list of values in which the order of values does not matter. Possible values are: code, token, id_token, otp, none. The \"none\" value cannot be used with any other response type value.",
+ "type": "string"
+ },
+ "client_id": {
+ "description": "OAuth2 client identifier",
+ "type": "string"
+ },
+ "redirect_uri": {
+ "description": "Redirect URI",
+ "type": "string",
+ "format": "uri"
+ },
+ "scope": {
+ "description": "Space-delimited scopes",
+ "type": "string"
+ },
+ "state": {
+ "description": "Opaque state parameter",
+ "type": "string"
+ },
+ "approval_prompt": {
+ "description": "Indicates whether the user should be re-prompted for consent. The default is auto, so a given user should only see the consent page for a given set of scopes the first time through the sequence. If the value is force, then the user sees a consent page even if they previously gave consent to your application for a given set of scopes.",
+ "type": "string",
+ "enum": [
+ "auto",
+ "force"
+ ]
+ },
+ "access_type": {
+ "description": "Indicates whether your application needs to access an API when the user is not present at the browser. This parameter defaults to online. If your application needs to refresh access tokens when the user is not present at the browser, then use offline. This will result in your application obtaining a refresh token the first time your application exchanges an authorization code for a user.",
+ "type": "string",
+ "enum": [
+ "online",
+ "offline"
+ ]
+ },
+ "response_mode": {
+ "description": "OPTIONAL. Informs the Authorization Server of the mechanism to be used for returning Authorization Response parameters from the Authorization Endpoint. This use of this parameter is NOT RECOMMENDED with a value that specifies the same Response Mode as the default Response Mode for the Response Type used.\\nThe default Response Mode for the OAuth 2.0 code Response Type is the query encoding. For purposes of this specification, the default Response Mode for the OAuth 2.0 token Response Type is the fragment encoding.",
+ "type": "string",
+ "enum": [
+ "query",
+ "fragment",
+ "form_post",
+ "direct"
+ ]
+ },
+ "code_challenge": {
+ "description": "PKCE code challenge",
+ "type": "string"
+ },
+ "code_challenge_method": {
+ "description": "Optional. PKCE challenge method",
+ "type": "string",
+ "enum": [
+ "plain",
+ "S256"
+ ]
+ },
+ "display": {
+ "description": "UI display preference (OIDC)",
+ "type": "string",
+ "enum": [
+ "page",
+ "popup",
+ "touch",
+ "wap",
+ "native"
+ ]
+ },
+ "tenant": {
+ "description": "Tenant identifier",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "OAuth2EndSessionRequest": {
+ "title": "OAuth2 End Session Request",
+ "description": "RP-Initiated Logout request parameters per OpenID Connect Session Management 1.0",
+ "required": [
+ "client_id"
+ ],
+ "properties": {
+ "client_id": {
+ "description": "OAuth2 client identifier",
+ "type": "string"
+ },
+ "id_token_hint": {
+ "description": "Previously issued ID token",
+ "type": "string"
+ },
+ "post_logout_redirect_uri": {
+ "description": "URI to redirect after logout",
+ "type": "string",
+ "format": "uri"
+ },
+ "state": {
+ "description": "Opaque state parameter",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "OAuth2TokenIntrospectionRequest": {
+ "title": "OAuth2 Token Introspection Request",
+ "description": "Token introspection request parameters per RFC 7662 §2.1",
+ "required": [
+ "token"
+ ],
+ "properties": {
+ "token": {
+ "description": "The token to introspect",
+ "type": "string"
+ },
+ "client_id": {
+ "description": "Client identifier (if not using HTTP Basic auth)",
+ "type": "string"
+ },
+ "client_secret": {
+ "description": "Client secret (if not using HTTP Basic auth)",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "OAuth2TokenRequest": {
+ "title": "OAuth2 Token Request",
+ "description": "Token request parameters per RFC 6749 §4.1.3. Fields required vary by grant_type.",
+ "required": [
+ "grant_type"
+ ],
+ "properties": {
+ "grant_type": {
+ "description": "OAuth2 grant type",
+ "type": "string",
+ "enum": [
+ "authorization_code",
+ "client_credentials",
+ "implicit",
+ "refresh_token",
+ "passwordless",
+ "hybrid"
+ ]
+ },
+ "code": {
+ "description": "Authorization code (authorization_code grant)",
+ "type": "string"
+ },
+ "code_verifier": {
+ "description": "PKCE code verifier (authorization_code grant with PKCE)",
+ "type": "string"
+ },
+ "redirect_uri": {
+ "description": "Redirect URI (must match the one used in authorization request)",
+ "type": "string",
+ "format": "uri"
+ },
+ "client_id": {
+ "description": "Client identifier (if not using HTTP Basic auth)",
+ "type": "string"
+ },
+ "client_secret": {
+ "description": "Client secret (if not using HTTP Basic auth)",
+ "type": "string"
+ },
+ "refresh_token": {
+ "description": "Refresh token (refresh_token grant)",
+ "type": "string"
+ },
+ "scope": {
+ "description": "Space-delimited scopes",
+ "type": "string"
+ },
+ "audience": {
+ "description": "Target audience (client_credentials grant)",
+ "type": "string"
+ },
+ "connection": {
+ "description": "Connection type (passwordless grant)",
+ "type": "string",
+ "enum": [
+ "sms",
+ "email"
+ ]
+ },
+ "otp": {
+ "description": "One-time password code (passwordless grant)",
+ "type": "string"
+ },
+ "email": {
+ "description": "Email address (passwordless grant, connection=email)",
+ "type": "string"
+ },
+ "phone_number": {
+ "description": "Phone number (passwordless grant, connection=sms)",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "OAuth2TokenRevocationRequest": {
+ "title": "OAuth2 Token Revocation Request",
+ "description": "Token revocation request parameters per RFC 7009 §2.1",
+ "required": [
+ "token"
+ ],
+ "properties": {
+ "token": {
+ "description": "The token to revoke",
+ "type": "string"
+ },
+ "token_type_hint": {
+ "description": "Hint about the token type",
+ "type": "string",
+ "enum": [
+ "access_token",
+ "refresh_token"
+ ]
+ },
+ "client_id": {
+ "description": "Client identifier (if not using HTTP Basic auth)",
+ "type": "string"
+ },
+ "client_secret": {
+ "description": "Client secret (if not using HTTP Basic auth)",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2ProviderSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 client credentials authentication for protocol endpoints (token, revoke, introspection)",
+ "flows": {
+ "clientCredentials": {
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {}
+ }
+ }
+ },
+ "OAuth2ProviderClientBasic": {
+ "type": "http",
+ "description": "HTTP Basic authentication with OAuth2 client_id:client_secret (RFC 6749 §2.3.1).",
+ "scheme": "basic"
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ },
+ {
+ "name": "OAuth2 / OpenID Connect",
+ "description": "OAuth2 / OpenID Connect"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-113/index.html b/openapi/pr-113/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-113/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-119/api-docs.json b/openapi/pr-119/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-119/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-119/index.html b/openapi/pr-119/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-119/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-120/api-docs.json b/openapi/pr-120/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-120/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-120/index.html b/openapi/pr-120/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-120/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-121/api-docs.json b/openapi/pr-121/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-121/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-121/index.html b/openapi/pr-121/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-121/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-122/api-docs.json b/openapi/pr-122/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-122/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-122/index.html b/openapi/pr-122/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-122/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-123/api-docs.json b/openapi/pr-123/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-123/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-123/index.html b/openapi/pr-123/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-123/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-124/api-docs.json b/openapi/pr-124/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-124/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-124/index.html b/openapi/pr-124/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-124/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-125/api-docs.json b/openapi/pr-125/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-125/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-125/index.html b/openapi/pr-125/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-125/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-126/api-docs.json b/openapi/pr-126/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-126/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-126/index.html b/openapi/pr-126/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-126/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-127/api-docs.json b/openapi/pr-127/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-127/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-127/index.html b/openapi/pr-127/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-127/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-128/api-docs.json b/openapi/pr-128/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-128/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-128/index.html b/openapi/pr-128/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-128/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-129/api-docs.json b/openapi/pr-129/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-129/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-129/index.html b/openapi/pr-129/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-129/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-130/api-docs.json b/openapi/pr-130/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-130/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-130/index.html b/openapi/pr-130/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-130/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-131/api-docs.json b/openapi/pr-131/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-131/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-131/index.html b/openapi/pr-131/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-131/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-132/api-docs.json b/openapi/pr-132/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-132/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-132/index.html b/openapi/pr-132/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-132/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-133/api-docs.json b/openapi/pr-133/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-133/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-133/index.html b/openapi/pr-133/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-133/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-134/api-docs.json b/openapi/pr-134/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-134/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-134/index.html b/openapi/pr-134/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-134/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-135/api-docs.json b/openapi/pr-135/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-135/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-135/index.html b/openapi/pr-135/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-135/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-136/api-docs.json b/openapi/pr-136/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-136/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-136/index.html b/openapi/pr-136/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-136/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-137/api-docs.json b/openapi/pr-137/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-137/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-137/index.html b/openapi/pr-137/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-137/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-138/api-docs.json b/openapi/pr-138/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-138/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-138/index.html b/openapi/pr-138/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-138/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-139/api-docs.json b/openapi/pr-139/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-139/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-139/index.html b/openapi/pr-139/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-139/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-140/api-docs.json b/openapi/pr-140/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-140/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-140/index.html b/openapi/pr-140/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-140/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-141/api-docs.json b/openapi/pr-141/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-141/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-141/index.html b/openapi/pr-141/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-141/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-142/api-docs.json b/openapi/pr-142/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-142/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-142/index.html b/openapi/pr-142/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-142/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-143/api-docs.json b/openapi/pr-143/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-143/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-143/index.html b/openapi/pr-143/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-143/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-144/api-docs.json b/openapi/pr-144/api-docs.json
new file mode 100644
index 00000000..de2f0685
--- /dev/null
+++ b/openapi/pr-144/api-docs.json
@@ -0,0 +1,2191 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "IDP API",
+ "description": "IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "FNTECH",
+ "email": "support@fntech.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v1/sso/disqus/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Disqus SSO"
+ ],
+ "summary": "Get Disqus user profile for a forum",
+ "operationId": "getDisqusUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DisqusUserProfileSerialized"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ },
+ "security": [
+ {
+ "OAuth2DisqusSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/groups": {
+ "get": {
+ "tags": [
+ "Groups"
+ ],
+ "summary": "Get all groups",
+ "description": "Retrieves a paginated list of groups with optional filtering and ordering. No route-level middleware enforcement; requires valid OAuth2 bearer token only.",
+ "operationId": "getGroups",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "minimum": 1,
+ "example": 1
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Number of items per page",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5,
+ "maximum": 100,
+ "minimum": 5,
+ "example": 10
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria. Supported filters: slug== (exact match). Example: filter=slug==administrators",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "slug==administrators"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Ordering criteria. Supported fields: id, name, slug. Use + for ascending, - for descending. Example: +name or -id",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "example": "+name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with paginated groups",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedGroupResponse"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation failed, invalid filter or order parameter"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2GroupsSecurity": [
+ "groups/read-all"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/rocket-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Rocket Chat SSO"
+ ],
+ "summary": "Get Rocket Chat user profile for a forum.",
+ "description": "Returns Rocket Chat user profile data for the authenticated user in the context of the specified forum. The content of the response is defined by \"data\" portion of the Rocket Chat login endpoint response structure",
+ "operationId": "getRocketChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK, returns Rocket Chat user profile data on login success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RocketChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2RocketChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/sso/stream-chat/{forum_slug}/profile": {
+ "get": {
+ "tags": [
+ "Stream Chat SSO"
+ ],
+ "summary": "Get Stream Chat user profile for a forum",
+ "description": "Returns the Stream Chat user profile and authentication token for the given forum. Requires SSO scope.",
+ "operationId": "getStreamChatUserProfile",
+ "parameters": [
+ {
+ "name": "forum_slug",
+ "in": "path",
+ "description": "Forum slug",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StreamChatUserProfile"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Error"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "401": {
+ "description": "Unauthorized - invalid or missing token"
+ },
+ "403": {
+ "description": "Forbidden - insufficient scope"
+ }
+ },
+ "security": [
+ {
+ "OAuth2StreamChatSSOSecurity": [
+ "sso"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get all users",
+ "operationId": "getUsers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page (5-100)",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter (first_name, last_name, email, primary_email)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserResponse"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Create a new user",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "User data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user basic info",
+ "description": "Gets User Basic Info",
+ "operationId": "getCurrentUser",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user",
+ "operationId": "updateCurrentUser",
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{id}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get a user by ID",
+ "operationId": "getUserById",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update a user by ID",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/me/pic": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update current user profile picture",
+ "operationId": "updateCurrentUserProfilePicture",
+ "requestBody": {
+ "description": "Profile picture file",
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserPicRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "me/write"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/users/info": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfo",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Get current user info (OpenID Connect UserInfo)",
+ "operationId": "getUserInfoPost",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserInfoResponse"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "profile"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "email"
+ ]
+ },
+ {
+ "OAuth2UserSecurity": [
+ "address"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/users/{id}/groups": {
+ "put": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Update user group assignments",
+ "operationId": "updateUserGroups",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Group IDs to assign",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserGroupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "412": {
+ "description": "Validation Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users/groups/write"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ },
+ "/api/v1/user-registration-requests": {
+ "get": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Get all user registration requests",
+ "description": "Class OAuth2UserRegistrationRequestApiController",
+ "operationId": "getUserRegistrationRequests",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "per_page",
+ "in": "query",
+ "description": "Items per page",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filter criteria (first_name, last_name, email, is_redeemed) (\"=@\" starts with, \"==\" exact match); is_redeemed supports \"==\" only.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "description": "Order criteria. Accepted fields: id. Use +id for ascending, -id for descending.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PaginatedUserRegistrationRequestResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Create a user registration request",
+ "operationId": "createUserRegistrationRequest",
+ "requestBody": {
+ "description": "User registration request data",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ },
+ "/api/v1/user-registration-requests/{id}": {
+ "put": {
+ "tags": [
+ "User Registration Requests"
+ ],
+ "summary": "Update a user registration request",
+ "operationId": "updateUserRegistrationRequest",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "Registration request ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "User registration request data to update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRegistrationRequestRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "412": {
+ "description": "Precondition Failed"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserRegistrationRequestApi": [
+ "user-registration"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "DisqusUserProfileSerialized": {
+ "description": "Disqus SSO user profile",
+ "properties": {
+ "auth": {
+ "description": "Base64 encoded profile JSON + space + hash + space + timestamp.",
+ "type": "string"
+ },
+ "public_key": {
+ "description": "Public key",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "RocketChatUserProfile": {
+ "description": "Rocket Chat SSO user profile. The response structure is the \"data\" portion of the Rocket Chat /api/v1/login endpoint response and is defined by the external Rocket Chat server.",
+ "type": "object",
+ "additionalProperties": true
+ },
+ "StreamChatUserProfile": {
+ "description": "Stream Chat SSO user profile",
+ "properties": {
+ "id": {
+ "description": "User ID",
+ "type": "string"
+ },
+ "name": {
+ "description": "Display name",
+ "type": "string"
+ },
+ "image": {
+ "description": "Avatar URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "token": {
+ "description": "Stream Chat JWT token",
+ "type": "string"
+ },
+ "api_key": {
+ "description": "Stream Chat API key",
+ "type": "string"
+ },
+ "local_role": {
+ "description": "User role in the forum",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoAddressClaim": {
+ "title": "Address Claim",
+ "description": "OpenID Connect Address Claim (RFC 5.1.1)",
+ "properties": {
+ "country": {
+ "description": "Country name",
+ "type": "string"
+ },
+ "street_address": {
+ "description": "Full street address component",
+ "type": "string"
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "Zip code or postal code",
+ "type": "string"
+ },
+ "region": {
+ "description": "State, province, or region",
+ "type": "string"
+ },
+ "locality": {
+ "description": "City or locality",
+ "type": "string"
+ },
+ "formatted": {
+ "description": "Full mailing address, formatted for display",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "UserInfoResponse": {
+ "title": "UserInfo Response",
+ "description": "OpenID Connect UserInfo endpoint response. Claims returned depend on the requested scopes (profile, email, address).",
+ "required": [
+ "sub",
+ "aud"
+ ],
+ "properties": {
+ "sub": {
+ "description": "Subject identifier for the End-User",
+ "type": "string"
+ },
+ "aud": {
+ "description": "Audience (client ID)",
+ "type": "string"
+ },
+ "name": {
+ "description": "Full name",
+ "type": "string"
+ },
+ "given_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "preferred_username": {
+ "description": "Preferred username",
+ "type": "string"
+ },
+ "family_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "nickname": {
+ "description": "Casual name or identifier",
+ "type": "string"
+ },
+ "picture": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ },
+ "birthdate": {
+ "description": "Date of birth",
+ "type": "string"
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string"
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string"
+ },
+ "locale": {
+ "description": "Preferred language",
+ "type": "string"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string"
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string"
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string"
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string"
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string"
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string"
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string"
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string"
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Whether to show the email or not",
+ "type": "boolean"
+ },
+ "address": {
+ "$ref": "#/components/schemas/UserInfoAddressClaim"
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequest": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string"
+ },
+ "first_name": {
+ "description": "First name",
+ "type": "string"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string"
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string"
+ },
+ "hash": {
+ "description": "Registration request hash",
+ "type": "string"
+ },
+ "set_password_link": {
+ "description": "Link to set password",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedGroupResponse": {
+ "description": "Paginated list of groups",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "description": "Array of group objects",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "PaginatedUserRegistrationRequestResponse": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/PaginateDataSchemaResponse"
+ },
+ {
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/UserRegistrationRequest"
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRegistrationRequestRequest": {
+ "title": "Create User Registration Request",
+ "description": "Request body for creating a user registration request",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Email address",
+ "type": "string",
+ "format": "email",
+ "maxLength": 255
+ },
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "CreateUserRequest": {
+ "title": "Create User Request",
+ "description": "Request body for creating a new user. Only email is required, all other fields are optional.",
+ "required": [
+ "email"
+ ],
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UpdateUserGroupsRequest": {
+ "title": "Update User Groups Request",
+ "description": "Request body for updating user group assignments",
+ "required": [
+ "groups"
+ ],
+ "properties": {
+ "groups": {
+ "description": "Array of group IDs to assign to the user",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserPicRequest": {
+ "title": "Update User Profile Picture Request",
+ "description": "Request body for uploading a user profile picture",
+ "required": [
+ "file"
+ ],
+ "properties": {
+ "file": {
+ "description": "Profile picture image file",
+ "type": "string",
+ "format": "binary"
+ }
+ },
+ "type": "object"
+ },
+ "UpdateUserRegistrationRequestRequest": {
+ "title": "Update User Registration Request",
+ "description": "Request body for updating a user registration request. All fields are optional.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserRegistrationRequestFields"
+ },
+ {
+ "properties": {
+ "country": {
+ "description": "Country ISO alpha-2 code",
+ "type": "string",
+ "maxLength": 2,
+ "minLength": 2,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "UpdateUserRequest": {
+ "title": "Update User Request",
+ "description": "Request body for updating a user. All fields are optional. Note: groups, email_verified, and active fields are automatically removed from non-admin requests.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/UserFields"
+ }
+ ]
+ },
+ "UserFields": {
+ "title": "User Fields",
+ "description": "Common user fields used across user operations",
+ "properties": {
+ "first_name": {
+ "description": "User first name",
+ "type": "string",
+ "example": "John",
+ "nullable": true
+ },
+ "last_name": {
+ "description": "User last name",
+ "type": "string",
+ "example": "Doe",
+ "nullable": true
+ },
+ "gender": {
+ "description": "User gender",
+ "type": "string",
+ "example": "Male",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Custom gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Birthday as Unix timestamp (seconds since epoch)",
+ "type": "integer",
+ "example": 631152000,
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "example": "en",
+ "nullable": true
+ },
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.doe@example.com"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.work@example.com",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "example": "john.alt@example.com",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "example": "+1-555-0123",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string",
+ "example": "123 Main Street",
+ "nullable": true
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "example": "Apt 4B",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string",
+ "example": "San Francisco",
+ "nullable": true
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string",
+ "example": "CA",
+ "nullable": true
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string",
+ "example": "94102",
+ "nullable": true
+ },
+ "country_iso_code": {
+ "description": "ISO 3166-1 alpha-2 country code",
+ "type": "string",
+ "example": "US",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "example": "Acme Corp",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title (max 200 characters)",
+ "type": "string",
+ "maxLength": 200,
+ "example": "Software Engineer",
+ "nullable": true
+ },
+ "bio": {
+ "description": "User biography (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Passionate developer with 10 years of experience",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest (HTML content will be sanitized)",
+ "type": "string",
+ "example": "Interested in cloud computing and open source",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC nickname",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter username",
+ "type": "string",
+ "example": "@johndoe",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "example": "https://linkedin.com/in/johndoe",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "example": "johndoe",
+ "nullable": true
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media information in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_bio": {
+ "description": "Show biography in public profile",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow others to chat with me",
+ "type": "boolean",
+ "example": true
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone number in public profile",
+ "type": "boolean",
+ "example": false
+ },
+ "password": {
+ "description": "Password (must meet password policy requirements)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "password_confirmation": {
+ "description": "Password confirmation (required when password is provided)",
+ "type": "string",
+ "example": "SecureP@ssw0rd"
+ },
+ "current_password": {
+ "description": "Current password (required when changing password for non-admin users)",
+ "type": "string",
+ "example": "OldP@ssw0rd"
+ },
+ "groups": {
+ "description": "Array of group IDs to assign (admin only, requires users/write scope)",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "example": [
+ 1,
+ 2,
+ 3
+ ]
+ },
+ "email_verified": {
+ "description": "Email verification status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "active": {
+ "description": "Account active status (admin only, requires users/write scope; ignored on update for non-admin users)",
+ "type": "boolean",
+ "example": true,
+ "nullable": true
+ },
+ "identifier": {
+ "description": "User identifier",
+ "type": "string",
+ "example": "user-12345",
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
+ "UserRegistrationRequestFields": {
+ "title": "User Registration Request Fields",
+ "description": "Common fields for user registration request operations",
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "maxLength": 100,
+ "nullable": true
+ }
+ },
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "OAuth2DisqusSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Disqus SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2GroupsSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Group endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "groups/read-all": "Read all groups",
+ "groups/write": "Write group"
+ }
+ }
+ }
+ },
+ "OAuth2RocketChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Rocket Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2StreamChatSSOSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 authentication for Stream Chat SSO endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "sso": "Single Sign-On access"
+ }
+ }
+ }
+ },
+ "OAuth2UserRegistrationRequestApi": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "user-registration": "User registration"
+ }
+ }
+ }
+ },
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "profile": "Read User Profile",
+ "email": "Read User Email",
+ "address": "Read User Address",
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write Current User Data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Write User Group Assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Disqus SSO",
+ "description": "Disqus SSO"
+ },
+ {
+ "name": "Groups",
+ "description": "Groups"
+ },
+ {
+ "name": "Rocket Chat SSO",
+ "description": "Rocket Chat SSO"
+ },
+ {
+ "name": "Stream Chat SSO",
+ "description": "Stream Chat SSO"
+ },
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ },
+ {
+ "name": "User Registration Requests",
+ "description": "User Registration Requests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-144/index.html b/openapi/pr-144/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-144/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/openapi/pr-98/api-docs.json b/openapi/pr-98/api-docs.json
new file mode 100644
index 00000000..be170fbd
--- /dev/null
+++ b/openapi/pr-98/api-docs.json
@@ -0,0 +1,416 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "OpenStackID API",
+ "description": "OpenStackID IDP API Documentation - OAuth2, OpenID Connect, and User Management",
+ "contact": {
+ "name": "OpenStack Foundation",
+ "email": "support@openstack.org"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "IDP API Server"
+ }
+ ],
+ "paths": {
+ "/api/v2/users/{id}": {
+ "get": {
+ "tags": [
+ "Users",
+ "V2"
+ ],
+ "summary": "Get a user by ID",
+ "description": "Retrieves user details by their numeric ID.",
+ "operationId": "getUserByIdV2",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "User ID",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "expand",
+ "in": "query",
+ "description": "Expand relations: groups",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Server Error"
+ },
+ "403": {
+ "description": "Forbidden - Only service accounts are allowed"
+ }
+ },
+ "security": [
+ {
+ "OAuth2UserSecurity": [
+ "users-read-all"
+ ]
+ }
+ ],
+ "x-x-required-client-type": "SERVICE"
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PaginateDataSchemaResponse": {
+ "description": "Base pagination metadata",
+ "properties": {
+ "total": {
+ "type": "integer",
+ "example": 6
+ },
+ "per_page": {
+ "type": "integer",
+ "example": 5
+ },
+ "current_page": {
+ "type": "integer",
+ "example": 1
+ },
+ "last_page": {
+ "type": "integer",
+ "example": 2
+ }
+ },
+ "type": "object"
+ },
+ "Base": {
+ "description": "Base serializer fields",
+ "properties": {
+ "id": {
+ "description": "Unique identifier",
+ "type": "integer",
+ "example": 1
+ },
+ "created_at": {
+ "description": "Creation timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ },
+ "updated_at": {
+ "description": "Last update timestamp (epoch)",
+ "type": "integer",
+ "example": 1609459200
+ }
+ },
+ "type": "object"
+ },
+ "BaseUser": {
+ "title": "Base User",
+ "description": "Base User serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "first_name": {
+ "description": "First name",
+ "type": "string",
+ "example": "John"
+ },
+ "last_name": {
+ "description": "Last name",
+ "type": "string",
+ "example": "Doe"
+ },
+ "pic": {
+ "description": "Profile picture URL",
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "Group": {
+ "title": "Group",
+ "description": "Group serialized representation",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Base"
+ },
+ {
+ "properties": {
+ "name": {
+ "description": "Group name",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Group slug",
+ "type": "string"
+ },
+ "active": {
+ "description": "Whether the group is active",
+ "type": "boolean"
+ },
+ "default": {
+ "description": "Whether the group is a default group",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ]
+ },
+ "User": {
+ "title": "User",
+ "description": "User serialized representation (private)",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BaseUser"
+ },
+ {
+ "properties": {
+ "email": {
+ "description": "Primary email address",
+ "type": "string",
+ "format": "email"
+ },
+ "identifier": {
+ "description": "User unique identifier string",
+ "type": "string"
+ },
+ "email_verified": {
+ "description": "Whether the primary email is verified",
+ "type": "boolean"
+ },
+ "bio": {
+ "description": "User biography",
+ "type": "string",
+ "nullable": true
+ },
+ "address1": {
+ "description": "Address line 1",
+ "type": "string"
+ },
+ "address2": {
+ "description": "Address line 2",
+ "type": "string",
+ "nullable": true
+ },
+ "city": {
+ "description": "City",
+ "type": "string"
+ },
+ "state": {
+ "description": "State or province",
+ "type": "string"
+ },
+ "post_code": {
+ "description": "Postal code",
+ "type": "string"
+ },
+ "country_iso_code": {
+ "description": "ISO country code",
+ "type": "string"
+ },
+ "second_email": {
+ "description": "Secondary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "third_email": {
+ "description": "Tertiary email address",
+ "type": "string",
+ "format": "email",
+ "nullable": true
+ },
+ "gender": {
+ "description": "Gender",
+ "type": "string",
+ "nullable": true
+ },
+ "gender_specify": {
+ "description": "Gender specification",
+ "type": "string",
+ "nullable": true
+ },
+ "statement_of_interest": {
+ "description": "Statement of interest",
+ "type": "string",
+ "nullable": true
+ },
+ "irc": {
+ "description": "IRC handle",
+ "type": "string",
+ "nullable": true
+ },
+ "linked_in_profile": {
+ "description": "LinkedIn profile URL",
+ "type": "string",
+ "nullable": true
+ },
+ "github_user": {
+ "description": "GitHub username",
+ "type": "string",
+ "nullable": true
+ },
+ "wechat_user": {
+ "description": "WeChat username",
+ "type": "string",
+ "nullable": true
+ },
+ "twitter_name": {
+ "description": "Twitter handle",
+ "type": "string",
+ "nullable": true
+ },
+ "language": {
+ "description": "Preferred language",
+ "type": "string",
+ "nullable": true
+ },
+ "birthday": {
+ "description": "Date of birth (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "phone_number": {
+ "description": "Phone number",
+ "type": "string",
+ "nullable": true
+ },
+ "company": {
+ "description": "Company name",
+ "type": "string",
+ "nullable": true
+ },
+ "job_title": {
+ "description": "Job title",
+ "type": "string",
+ "nullable": true
+ },
+ "spam_type": {
+ "description": "Spam classification",
+ "type": "string",
+ "enum": [
+ "None",
+ "Spam",
+ "Ham"
+ ]
+ },
+ "last_login_date": {
+ "description": "Last login date (epoch)",
+ "type": "integer",
+ "nullable": true
+ },
+ "active": {
+ "description": "Whether the user account is active",
+ "type": "boolean"
+ },
+ "public_profile_show_photo": {
+ "description": "Show photo in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_fullname": {
+ "description": "Show full name in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_email": {
+ "description": "Show email in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_social_media_info": {
+ "description": "Show social media info in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_bio": {
+ "description": "Show bio in public profile",
+ "type": "boolean"
+ },
+ "public_profile_allow_chat_with_me": {
+ "description": "Allow chat in public profile",
+ "type": "boolean"
+ },
+ "public_profile_show_telephone_number": {
+ "description": "Show telephone in public profile",
+ "type": "boolean"
+ },
+ "groups": {
+ "description": "User groups (expandable with expand=groups)",
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "description": "Group slug (when not expanded)",
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Group"
+ }
+ ]
+ }
+ }
+ },
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "OAuth2UserSecurity": {
+ "type": "oauth2",
+ "description": "OAuth2 security scheme for user-related API endpoints",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "http://localhost/oauth/authorize",
+ "tokenUrl": "http://localhost/oauth/token",
+ "scopes": {
+ "users-read-all": "Read All Users Data",
+ "me/write": "Write current user data",
+ "users/write": "Write Users Data",
+ "users/groups/write": "Manage User Group assignments"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users",
+ "description": "Users"
+ },
+ {
+ "name": "V2",
+ "description": "V2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/openapi/pr-98/index.html b/openapi/pr-98/index.html
new file mode 100644
index 00000000..604cd811
--- /dev/null
+++ b/openapi/pr-98/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+ OpenStackID API - Swagger UI
+
+
+
+
+
+
+
+
diff --git a/robots.txt b/robots.txt
new file mode 100644
index 00000000..eb053628
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: