This is an automated email from the ASF dual-hosted git repository.

yasith pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata.git


The following commit(s) were added to refs/heads/master by this push:
     new 82506ff512 fix: sharing init, storage mapping, SDK facades, and dev 
infra (#614)
82506ff512 is described below

commit 82506ff512fa13290df1d5314fae765303a28d17
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Sat Apr 11 23:12:07 2026 -0500

    fix: sharing init, storage mapping, SDK facades, and dev infra (#614)
    
    * feat: airavata server fixes, SDK facades, auto-generated SFTP keys
    
    - Fix sharing EntityRepository null-safe casting
    - Fix ResearchMapper projectId/projectID mapping
    - Update credential store encryption and key generation
    - SDK facade clients updated for gRPC service alignment
    - Tiltfile auto-generates SSH keypair for SFTP container (no static keys in 
repo)
    - DevStorageInitializer reads keypair from conf/sftp/ instead of generating
    - Keycloak custom theme for dev login
    - Gitignore conf/sftp/id_* generated keys
    
    * fix: use proc check instead of ss for SFTP healthcheck (ss not available 
in image)
    
    * fix: map DataMovementInterfaceEntity to STORAGE_INTERFACE table instead 
of DATA_MOVEMENT_INTERFACE
---
 .gitignore                                         |   3 +
 Tiltfile                                           |  17 +-
 .../airavata/compute/util/SSHJStorageAdaptor.java  | 250 +++++++++++++----
 airavata-api/credential-service/pom.xml            |   9 +
 .../credential/model/CredentialEntity.java         |   2 +-
 .../apache/airavata/credential/util/Utility.java   |  23 +-
 airavata-api/iam-service/pom.xml                   |   6 +
 .../orchestration/util/DevStorageInitializer.java  | 135 ++++++++++
 .../orchestration/util/ExpCatalogDBInitConfig.java |  49 ++++
 .../airavata/research/mapper/ResearchMapper.java   |   2 +
 .../sharing/repository/EntityRepository.java       |   8 +-
 .../org/apache/airavata/grpc/GrpcStatusMapper.java |   8 +-
 .../apache/airavata/task/AdaptorSupportImpl.java   |   2 +-
 .../storage/grpc/UserStorageGrpcService.java       |  93 ++++++-
 .../airavata/storage/mapper/StorageMapper.java     |  18 +-
 .../storage/model/DataMovementInterfaceEntity.java |   4 +-
 airavata-python-sdk/airavata_sdk/client.py         |   7 +-
 airavata-python-sdk/airavata_sdk/facade/compute.py |  66 +++--
 .../airavata_sdk/facade/credential.py              |  18 +-
 airavata-python-sdk/airavata_sdk/facade/iam.py     |  15 +-
 .../airavata_sdk/facade/research.py                | 108 +++++---
 airavata-python-sdk/airavata_sdk/facade/sharing.py |  15 +-
 airavata-python-sdk/airavata_sdk/facade/storage.py |  15 +-
 .../src/main/resources/application.properties      |   4 +-
 .../db/migration/airavata/V1__Baseline_schema.sql  |   1 +
 compose.yml                                        |  21 ++
 conf/keycloak/realm-default.json                   |  53 +---
 .../themes/airavata/login/resources/css/custom.css | 297 +++++++++++++++++++++
 .../themes/airavata/login/resources/img/logo.png   | Bin 0 -> 5510 bytes
 .../themes/airavata/login/theme.properties         |   4 +
 pom.xml                                            |   7 +
 31 files changed, 1032 insertions(+), 228 deletions(-)

diff --git a/.gitignore b/.gitignore
index c73fc685c9..80e16aa986 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+# Generated dev SSH keypair (created by Tiltfile on startup)
+conf/sftp/id_*
+
 # From 
https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore
 # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
 
diff --git a/Tiltfile b/Tiltfile
index c7e5c93539..84a87cec15 100644
--- a/Tiltfile
+++ b/Tiltfile
@@ -1,6 +1,15 @@
 # Airavata Development Tiltfile
 # Usage: tilt up
 
+# --- Generate dev SSH keypair for SFTP container (idempotent) ---
+local(
+    'mkdir -p conf/sftp && '
+    'test -f conf/sftp/id_rsa || '
+    'ssh-keygen -t rsa -b 2048 -f conf/sftp/id_rsa -N "" -q && '
+    'echo "Generated dev SSH keypair at conf/sftp/id_rsa"',
+    quiet=True,
+)
+
 # --- Infrastructure (from compose.yml) ---
 docker_compose('./compose.yml')
 
@@ -9,16 +18,10 @@ local_resource(
     'build',
     cmd='mvn install -DskipTests -Dmaven.test.skip=true -T4 -q',
     deps=[
-        'airavata-api/src',
-        'airavata-api/pom.xml',
-        'airavata-api/agent-service/src',
-        'airavata-api/agent-service/pom.xml',
-        'airavata-api/research-service/src',
-        'airavata-api/research-service/pom.xml',
-        'airavata-server/src',
         'airavata-server/pom.xml',
     ],
     ignore=['**/target/**'],
+    trigger_mode=TRIGGER_MODE_AUTO,
     labels=['build'],
 )
 
diff --git 
a/airavata-api/compute-service/src/main/java/org/apache/airavata/compute/util/SSHJStorageAdaptor.java
 
b/airavata-api/compute-service/src/main/java/org/apache/airavata/compute/util/SSHJStorageAdaptor.java
index a5ecbf18a7..1d1e00289e 100644
--- 
a/airavata-api/compute-service/src/main/java/org/apache/airavata/compute/util/SSHJStorageAdaptor.java
+++ 
b/airavata-api/compute-service/src/main/java/org/apache/airavata/compute/util/SSHJStorageAdaptor.java
@@ -19,10 +19,24 @@
 */
 package org.apache.airavata.compute.util;
 
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
 import java.util.Optional;
+
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.sftp.FileAttributes;
+import net.schmizz.sshj.sftp.FileMode;
+import net.schmizz.sshj.sftp.RemoteResourceInfo;
+import net.schmizz.sshj.sftp.SFTPClient;
+import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
+import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
+
 import org.apache.airavata.interfaces.AgentException;
 import org.apache.airavata.interfaces.CommandOutput;
-import org.apache.airavata.interfaces.SSHConnectionService;
+import org.apache.airavata.interfaces.FileMetadata;
 import org.apache.airavata.interfaces.StorageResourceAdaptor;
 import 
org.apache.airavata.model.appcatalog.storageresource.proto.StorageResourceDescription;
 import org.apache.airavata.model.credential.store.proto.SSHCredential;
@@ -32,93 +46,223 @@ import 
org.apache.airavata.model.data.movement.proto.SCPDataMovement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class SSHJStorageAdaptor extends SSHJAgentAdaptor implements 
StorageResourceAdaptor {
-
-    private static final Logger logger = 
LoggerFactory.getLogger(SSHJStorageAdaptor.class);
+/**
+ * Simple SFTP-based storage adaptor using SSHJ directly.
+ * No connection pooling, no abstract hierarchy — just SSH + SFTP.
+ */
+public class SSHJStorageAdaptor implements StorageResourceAdaptor {
 
-    public SSHJStorageAdaptor() {}
+    private static final Logger log = 
LoggerFactory.getLogger(SSHJStorageAdaptor.class);
 
-    public SSHJStorageAdaptor(SSHConnectionService sshConnectionService) {
-        super(sshConnectionService);
-    }
+    private String host;
+    private int port;
+    private String username;
+    private String privateKey;
+    private String passphrase;
 
     @Override
     public void init(String storageResourceId, String gatewayId, String 
loginUser, String token) throws AgentException {
         try {
-            logger.info("Initializing Storage Resource SCP Adaptor for storage 
resource : " + storageResourceId
-                    + ", gateway : " + gatewayId + ", user " + loginUser + ", 
token : " + token);
+            log.info("Initializing SFTP adaptor: resource={}, gateway={}, 
user={}", storageResourceId, gatewayId, loginUser);
+
+            StorageResourceDescription sr = 
AgentUtils.getRegistryServiceClient().getStorageResource(storageResourceId);
+
+            Optional<DataMovementInterface> dmOp = 
sr.getDataMovementInterfacesList().stream()
+                    .filter(iface -> iface.getDataMovementProtocol() == 
DataMovementProtocol.SCP)
+                    .findFirst();
+
+            DataMovementInterface dm = dmOp.orElseThrow(() ->
+                    new AgentException("No SCP data movement interface for 
storage resource " + storageResourceId));
 
-            StorageResourceDescription storageResourceDescription =
-                    
AgentUtils.getRegistryServiceClient().getStorageResource(storageResourceId);
+            SCPDataMovement scp = 
AgentUtils.getRegistryServiceClient().getSCPDataMovement(dm.getDataMovementInterfaceId());
 
-            logger.info("Fetching data movement interfaces for storage 
resource " + storageResourceId);
+            SSHCredential cred = 
AgentUtils.getCredentialClient().getSSHCredential(token, gatewayId);
+            if (cred == null) throw new AgentException("No credential for 
token " + token);
 
-            Optional<DataMovementInterface> dmInterfaceOp =
-                    
storageResourceDescription.getDataMovementInterfacesList().stream()
-                            .filter(iface -> iface.getDataMovementProtocol() 
== DataMovementProtocol.SCP)
-                            .findFirst();
+            String altHost = scp.getAlternativeScpHostName();
+            this.host = (altHost != null && !altHost.isEmpty()) ? altHost : 
sr.getHostName();
+            this.port = scp.getSshPort() == 0 ? 22 : scp.getSshPort();
+            this.username = loginUser;
+            this.privateKey = cred.getPrivateKey();
+            this.passphrase = cred.getPassphrase();
 
-            DataMovementInterface scpInterface = dmInterfaceOp.orElseThrow(() 
->
-                    new AgentException("Could not find a SCP interface for 
storage resource " + storageResourceId));
+            log.info("SFTP adaptor ready: {}@{}:{}", username, host, port);
 
-            SCPDataMovement scpDataMovement =
-                    
AgentUtils.getRegistryServiceClient().getSCPDataMovement(scpInterface.getDataMovementInterfaceId());
+        } catch (Exception e) {
+            log.error("Failed to init SFTP adaptor for " + storageResourceId, 
e);
+            throw new AgentException("Failed to init SFTP adaptor for " + 
storageResourceId, e);
+        }
+    }
+
+    private SFTPClient openSftp() throws Exception {
+        SSHClient ssh = new SSHClient();
+        ssh.addHostKeyVerifier(new PromiscuousVerifier());
+        ssh.connect(host, port);
 
-            logger.info("Fetching credentials for cred store token " + token);
+        // Write PEM key to temp file since SSHJ loadKeys expects a file path
+        java.io.File keyFile = java.io.File.createTempFile("airavata-ssh-", 
".pem");
+        try {
+            java.nio.file.Files.writeString(keyFile.toPath(), privateKey);
+            ssh.authPublickey(username, 
ssh.loadKeys(keyFile.getAbsolutePath()));
+        } finally {
+            keyFile.delete();
+        }
+
+        return ssh.newSFTPClient();
+    }
 
-            SSHCredential sshCredential = 
AgentUtils.getCredentialClient().getSSHCredential(token, gatewayId);
-            if (sshCredential == null) {
-                throw new AgentException("Null credential for token " + token);
+    @Override
+    public List<String> listDirectory(String path) throws AgentException {
+        try (SFTPClient sftp = openSftp()) {
+            List<RemoteResourceInfo> entries = sftp.ls(path);
+            List<String> names = new ArrayList<>();
+            for (RemoteResourceInfo entry : entries) {
+                names.add(entry.getName());
             }
+            return names;
+        } catch (Exception e) {
+            log.error("Failed to list directory: " + path, e);
+            throw new AgentException("Failed to list directory: " + path, e);
+        }
+    }
+
+    @Override
+    public Boolean doesFileExist(String filePath) throws AgentException {
+        try (SFTPClient sftp = openSftp()) {
+            sftp.stat(filePath);
+            return true;
+        } catch (net.schmizz.sshj.sftp.SFTPException e) {
+            if (e.getMessage().contains("No such file")) return false;
+            log.error("SFTP error checking file: " + filePath, e);
+            throw new AgentException("Failed to check file existence: " + 
filePath, e);
+        } catch (Exception e) {
+            log.error("Failed to check file existence: " + filePath, e);
+            throw new AgentException("Failed to check file existence: " + 
filePath, e);
+        }
+    }
 
-            logger.info("Description for token : " + token + " : " + 
sshCredential.getDescription());
+    @Override
+    public FileMetadata getFileMetadata(String remoteFile) throws 
AgentException {
+        try (SFTPClient sftp = openSftp()) {
+            FileAttributes attrs = sftp.stat(remoteFile);
+            FileMetadata meta = new FileMetadata();
+            meta.setName(remoteFile.substring(remoteFile.lastIndexOf('/') + 
1));
+            meta.setSize(attrs.getSize());
+            meta.setDirectory(attrs.getType() == FileMode.Type.DIRECTORY);
+            return meta;
+        } catch (Exception e) {
+            throw new AgentException("Failed to get file metadata: " + 
remoteFile, e);
+        }
+    }
 
-            String alternateHostName = 
scpDataMovement.getAlternativeScpHostName();
-            String selectedHostName = (alternateHostName == null || 
"".equals(alternateHostName))
-                    ? storageResourceDescription.getHostName()
-                    : alternateHostName;
+    @Override
+    public 
org.apache.airavata.model.appcatalog.storageresource.proto.StorageDirectoryInfo 
getStorageDirectoryInfo(String location) throws AgentException {
+        return 
org.apache.airavata.model.appcatalog.storageresource.proto.StorageDirectoryInfo.getDefaultInstance();
+    }
 
-            int selectedPort = scpDataMovement.getSshPort() == 0 ? 22 : 
scpDataMovement.getSshPort();
+    @Override
+    public void createDirectory(String path) throws AgentException {
+        createDirectory(path, false);
+    }
 
-            // Use the parent's init method which delegates to 
SSHConnectionService
-            init(
-                    loginUser,
-                    selectedHostName,
-                    selectedPort,
-                    sshCredential.getPublicKey(),
-                    sshCredential.getPrivateKey(),
-                    sshCredential.getPassphrase());
+    @Override
+    public void createDirectory(String path, boolean recursive) throws 
AgentException {
+        try (SFTPClient sftp = openSftp()) {
+            if (recursive) {
+                sftp.mkdirs(path);
+            } else {
+                sftp.mkdir(path);
+            }
+        } catch (Exception e) {
+            throw new AgentException("Failed to create directory: " + path, e);
+        }
+    }
 
+    @Override
+    public void deleteDirectory(String path) throws AgentException {
+        try (SFTPClient sftp = openSftp()) {
+            sftp.rmdir(path);
         } catch (Exception e) {
-            logger.error(
-                    "Error while initializing ssh agent for storage resource " 
+ storageResourceId + " to token "
-                            + token,
-                    e);
-            throw new AgentException(
-                    "Error while initializing ssh agent for storage resource " 
+ storageResourceId + " to token "
-                            + token,
-                    e);
+            throw new AgentException("Failed to delete directory: " + path, e);
         }
     }
 
     @Override
     public void uploadFile(String localFile, String remoteFile) throws 
AgentException {
-        super.uploadFile(localFile, remoteFile);
+        try (SFTPClient sftp = openSftp()) {
+            sftp.put(localFile, remoteFile);
+        } catch (Exception e) {
+            throw new AgentException("Failed to upload file: " + localFile + " 
-> " + remoteFile, e);
+        }
+    }
+
+    @Override
+    public void uploadFile(InputStream localInStream, FileMetadata metadata, 
String remoteFile) throws AgentException {
+        // Write stream to temp file then upload
+        try (SFTPClient sftp = openSftp()) {
+            java.io.File tempFile = 
java.io.File.createTempFile("airavata-upload-", ".tmp");
+            try {
+                java.nio.file.Files.copy(localInStream, tempFile.toPath(), 
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+                sftp.put(tempFile.getAbsolutePath(), remoteFile);
+            } finally {
+                tempFile.delete();
+            }
+        } catch (Exception e) {
+            throw new AgentException("Failed to upload stream to: " + 
remoteFile, e);
+        }
     }
 
     @Override
     public void downloadFile(String remoteFile, String localFile) throws 
AgentException {
-        super.downloadFile(remoteFile, localFile);
+        try (SFTPClient sftp = openSftp()) {
+            sftp.get(remoteFile, localFile);
+        } catch (Exception e) {
+            throw new AgentException("Failed to download file: " + remoteFile, 
e);
+        }
+    }
+
+    @Override
+    public void downloadFile(String remoteFile, OutputStream localOutStream, 
FileMetadata metadata) throws AgentException {
+        try (SFTPClient sftp = openSftp()) {
+            java.io.File tempFile = 
java.io.File.createTempFile("airavata-download-", ".tmp");
+            try {
+                sftp.get(remoteFile, tempFile.getAbsolutePath());
+                java.nio.file.Files.copy(tempFile.toPath(), localOutStream);
+            } finally {
+                tempFile.delete();
+            }
+        } catch (Exception e) {
+            throw new AgentException("Failed to download file: " + remoteFile, 
e);
+        }
     }
 
     @Override
     public CommandOutput executeCommand(String command, String 
workingDirectory) throws AgentException {
-        return super.executeCommand(command, workingDirectory);
+        throw new AgentException("Command execution not supported on storage 
resources");
+    }
+
+    @Override
+    public List<String> getFileNameFromExtension(String fileName, String 
parentPath) throws AgentException {
+        List<String> result = new ArrayList<>();
+        try (SFTPClient sftp = openSftp()) {
+            for (RemoteResourceInfo entry : sftp.ls(parentPath)) {
+                if (entry.getName().endsWith(fileName)) {
+                    result.add(entry.getName());
+                }
+            }
+        } catch (Exception e) {
+            throw new AgentException("Failed to search files by extension: " + 
fileName, e);
+        }
+        return result;
+    }
+
+    @Override
+    public 
org.apache.airavata.model.appcatalog.storageresource.proto.StorageVolumeInfo 
getStorageVolumeInfo(String location) throws AgentException {
+        return 
org.apache.airavata.model.appcatalog.storageresource.proto.StorageVolumeInfo.getDefaultInstance();
     }
 
     @Override
-    public 
org.apache.airavata.model.appcatalog.storageresource.proto.StorageVolumeInfo 
getStorageVolumeInfo(
-            String location) throws AgentException {
-        return super.getStorageVolumeInfo(location);
+    public void destroy() {
+        // No persistent connections to clean up
     }
 }
diff --git a/airavata-api/credential-service/pom.xml 
b/airavata-api/credential-service/pom.xml
index 02152a7781..354ce0fdbb 100644
--- a/airavata-api/credential-service/pom.xml
+++ b/airavata-api/credential-service/pom.xml
@@ -44,6 +44,15 @@ under the License.
             <artifactId>iam-service</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <!-- BouncyCastle (for SSH key generation/PEM encoding) -->
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk18on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk18on</artifactId>
+        </dependency>
         <!-- SSH -->
         <dependency>
             <groupId>com.hierynomus</groupId>
diff --git 
a/airavata-api/credential-service/src/main/java/org/apache/airavata/credential/model/CredentialEntity.java
 
b/airavata-api/credential-service/src/main/java/org/apache/airavata/credential/model/CredentialEntity.java
index 6ecd8aa58d..f4c94cd246 100644
--- 
a/airavata-api/credential-service/src/main/java/org/apache/airavata/credential/model/CredentialEntity.java
+++ 
b/airavata-api/credential-service/src/main/java/org/apache/airavata/credential/model/CredentialEntity.java
@@ -36,7 +36,7 @@ public class CredentialEntity {
     private String tokenId;
 
     @Lob
-    @Column(name = "CREDENTIAL", nullable = false)
+    @Column(name = "CREDENTIAL", nullable = false, columnDefinition = 
"MEDIUMBLOB")
     private byte[] credential;
 
     @Column(name = "PORTAL_USER_ID", length = 256, nullable = false)
diff --git 
a/airavata-api/credential-service/src/main/java/org/apache/airavata/credential/util/Utility.java
 
b/airavata-api/credential-service/src/main/java/org/apache/airavata/credential/util/Utility.java
index 6326ad410c..528eef25db 100644
--- 
a/airavata-api/credential-service/src/main/java/org/apache/airavata/credential/util/Utility.java
+++ 
b/airavata-api/credential-service/src/main/java/org/apache/airavata/credential/util/Utility.java
@@ -24,6 +24,7 @@ import java.io.StringWriter;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.KeyStore;
+import java.security.Security;
 import java.security.interfaces.RSAPublicKey;
 import java.text.DateFormat;
 import java.text.ParseException;
@@ -45,6 +46,12 @@ public class Utility {
 
     protected static Logger log = LoggerFactory.getLogger(Utility.class);
 
+    static {
+        if (Security.getProvider("BC") == null) {
+            Security.addProvider(new 
org.bouncycastle.jce.provider.BouncyCastleProvider());
+        }
+    }
+
     private static final String DATE_FORMAT = "MM/dd/yyyy HH:mm:ss";
 
     public static String convertDateToString(Date date) {
@@ -94,22 +101,10 @@ public class Utility {
             kpg.initialize(2048);
             KeyPair kp = kpg.generateKeyPair();
 
-            // Encode private key in PEM format using BouncyCastle
+            // Encode private key in PEM format (unencrypted — credential 
store handles encryption at rest)
             StringWriter privateKeyWriter = new StringWriter();
             try (JcaPEMWriter pemWriter = new JcaPEMWriter(privateKeyWriter)) {
-                String passphrase = credential.getPassphrase();
-                if (passphrase != null && !passphrase.isEmpty()) {
-                    
org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder 
encryptorBuilder =
-                            new 
org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder(
-                                    
org.bouncycastle.asn1.nist.NISTObjectIdentifiers.id_aes256_CBC);
-                    encryptorBuilder.setPassword(passphrase.toCharArray());
-                    org.bouncycastle.operator.OutputEncryptor encryptor = 
encryptorBuilder.build();
-                    
org.bouncycastle.pkcs.jcajce.JcaPKCS8EncryptedPrivateKeyInfoBuilder 
pkcs8Builder =
-                            new 
org.bouncycastle.pkcs.jcajce.JcaPKCS8EncryptedPrivateKeyInfoBuilder(kp.getPrivate());
-                    pemWriter.writeObject(pkcs8Builder.build(encryptor));
-                } else {
-                    pemWriter.writeObject(new 
JcaMiscPEMGenerator(kp.getPrivate()));
-                }
+                pemWriter.writeObject(new 
JcaMiscPEMGenerator(kp.getPrivate()));
             }
             String privateKeyPem = privateKeyWriter.toString();
 
diff --git a/airavata-api/iam-service/pom.xml b/airavata-api/iam-service/pom.xml
index 7ec71abde0..a623bb9dd9 100644
--- a/airavata-api/iam-service/pom.xml
+++ b/airavata-api/iam-service/pom.xml
@@ -57,6 +57,12 @@ under the License.
             <optional>true</optional>
         </dependency>
 
+        <!-- MapStruct -->
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct</artifactId>
+        </dependency>
+
         <!-- Test dependencies -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
diff --git 
a/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/DevStorageInitializer.java
 
b/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/DevStorageInitializer.java
new file mode 100644
index 0000000000..4574be358c
--- /dev/null
+++ 
b/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/DevStorageInitializer.java
@@ -0,0 +1,135 @@
+package org.apache.airavata.orchestration.util;
+
+import jakarta.annotation.PostConstruct;
+import org.apache.airavata.config.ServerSettings;
+import org.apache.airavata.credential.service.CredentialStoreService;
+import 
org.apache.airavata.model.appcatalog.gatewayprofile.proto.StoragePreference;
+import 
org.apache.airavata.model.appcatalog.storageresource.proto.StorageResourceDescription;
+import org.apache.airavata.model.credential.store.proto.SSHCredential;
+import org.apache.airavata.model.data.movement.proto.DMType;
+import org.apache.airavata.model.data.movement.proto.DataMovementProtocol;
+import org.apache.airavata.model.data.movement.proto.SCPDataMovement;
+import org.apache.airavata.model.data.movement.proto.SecurityProtocol;
+import org.apache.airavata.orchestration.service.RegistryServerHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * Cold-start initializer that registers a dev SFTP storage resource, SSH 
credential,
+ * and gateway storage preference so the portal Storage page works out of the 
box.
+ *
+ * Reads the SSH keypair from conf/sftp/ (generated by Tiltfile on startup).
+ * Idempotent: checks if the storage resource already exists before creating.
+ */
+@Component
+@Order(200) // Run after ExpCatalogDBInitConfig and AppCatalogDBInitConfig
+public class DevStorageInitializer {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(DevStorageInitializer.class);
+
+    private static final String STORAGE_HOST = "localhost";
+    private static final String STORAGE_DESCRIPTION = "Dev SFTP storage 
(atmoz/sftp container)";
+    private static final String SFTP_LOGIN_USER = "airavata";
+    private static final String SFTP_FS_ROOT = "/storage";
+    private static final String CREDENTIAL_DESCRIPTION = "Dev SFTP";
+
+    @Value("${dev.sftp.private-key-path:conf/sftp/id_rsa}")
+    private String privateKeyPath;
+
+    @Value("${dev.sftp.public-key-path:conf/sftp/id_rsa.pub}")
+    private String publicKeyPath;
+
+    @Autowired
+    private RegistryServerHandler registryHandler;
+
+    @Autowired
+    private CredentialStoreService credentialStoreService;
+
+    @PostConstruct
+    public void initialize() {
+        try {
+            String gatewayId = ServerSettings.getDefaultUserGateway();
+            String defaultUser = ServerSettings.getDefaultUser();
+
+            // Check if a storage resource for this host already exists
+            var allNames = registryHandler.getAllStorageResourceNames();
+            for (var entry : allNames.entrySet()) {
+                if (STORAGE_HOST.equals(entry.getValue())) {
+                    logger.info("Dev storage resource already exists: {} 
({})", entry.getValue(), entry.getKey());
+                    return;
+                }
+            }
+
+            // 1. Register storage resource
+            StorageResourceDescription storageResource = 
StorageResourceDescription.newBuilder()
+                    .setHostName(STORAGE_HOST)
+                    .setStorageResourceDescription(STORAGE_DESCRIPTION)
+                    .setEnabled(true)
+                    .build();
+            String storageResourceId = 
registryHandler.registerStorageResource(storageResource);
+            logger.info("Registered dev storage resource: {} ({})", 
STORAGE_HOST, storageResourceId);
+
+            // 2. Add SCP data movement interface
+            SCPDataMovement scpDm = SCPDataMovement.newBuilder()
+                    .setSecurityProtocol(SecurityProtocol.SSH_KEYS)
+                    .setSshPort(22)
+                    .build();
+            registryHandler.addSCPDataMovementDetails(
+                    storageResourceId, DMType.STORAGE_RESOURCE, 0, scpDm);
+            logger.info("Added SCP data movement interface for storage: {}", 
storageResourceId);
+
+            // 3. Register SSH credential from Tiltfile-generated keypair
+            String privateKey = readKeyFile(privateKeyPath);
+            String publicKey = readKeyFile(publicKeyPath);
+            if (privateKey == null || publicKey == null) {
+                logger.warn("SSH keypair not found at {} / {} — run 'tilt up' 
to generate", privateKeyPath, publicKeyPath);
+                return;
+            }
+            SSHCredential sshCredential = SSHCredential.newBuilder()
+                    .setUsername(defaultUser)
+                    .setGatewayId(gatewayId)
+                    .setDescription(CREDENTIAL_DESCRIPTION)
+                    .setPrivateKey(privateKey)
+                    .setPublicKey(publicKey)
+                    .build();
+            String credentialToken = 
credentialStoreService.addSSHCredential(sshCredential);
+            logger.info("Registered SSH credential from {} for dev storage: 
{}", privateKeyPath, credentialToken);
+
+            // 4. Add gateway storage preference
+            StoragePreference storagePreference = 
StoragePreference.newBuilder()
+                    .setStorageResourceId(storageResourceId)
+                    .setLoginUserName(SFTP_LOGIN_USER)
+                    .setFileSystemRootLocation(SFTP_FS_ROOT)
+                    .setResourceSpecificCredentialStoreToken(credentialToken)
+                    .build();
+            registryHandler.addGatewayStoragePreference(gatewayId, 
storageResourceId, storagePreference);
+            logger.info("Added gateway storage preference for dev storage: 
{}", storageResourceId);
+
+            logger.info("Dev storage cold-start initialization complete");
+
+        } catch (Exception e) {
+            logger.warn("Dev storage initialization failed (non-fatal): {}", 
e.getMessage());
+            logger.debug("Dev storage init error details:", e);
+        }
+    }
+
+    private String readKeyFile(String path) {
+        try {
+            Path keyPath = Path.of(path);
+            if (Files.exists(keyPath)) {
+                return Files.readString(keyPath).trim();
+            }
+        } catch (IOException e) {
+            logger.warn("Failed to read key file {}: {}", path, 
e.getMessage());
+        }
+        return null;
+    }
+}
diff --git 
a/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/ExpCatalogDBInitConfig.java
 
b/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/ExpCatalogDBInitConfig.java
index 5c3783fbe2..5fbd5ddff3 100644
--- 
a/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/ExpCatalogDBInitConfig.java
+++ 
b/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/ExpCatalogDBInitConfig.java
@@ -23,20 +23,28 @@ import org.apache.airavata.config.ServerSettings;
 import org.apache.airavata.db.DBInitConfig;
 import org.apache.airavata.db.JDBCConfig;
 import org.apache.airavata.interfaces.GatewayRegistry;
+import org.apache.airavata.interfaces.SharingFacade;
 import org.apache.airavata.model.user.proto.UserProfile;
 import org.apache.airavata.model.workspace.proto.Gateway;
 import org.apache.airavata.model.workspace.proto.GatewayApprovalStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
 public class ExpCatalogDBInitConfig implements DBInitConfig {
 
+    private static final Logger logger = 
LoggerFactory.getLogger(ExpCatalogDBInitConfig.class);
+
     private String dbInitScriptPrefix = "database_scripts/expcatalog";
 
     @Autowired
     private GatewayRegistry gatewayRegistry;
 
+    @Autowired
+    private SharingFacade sharingFacade;
+
     @Override
     public JDBCConfig getJDBCConfig() {
         return null;
@@ -57,7 +65,44 @@ public class ExpCatalogDBInitConfig implements DBInitConfig {
         return "CONFIGURATION";
     }
 
+    private void initializeSharingForGateway(String gatewayId) {
+        // Domain
+        tryCreate("sharing domain", () ->
+                sharingFacade.createDomain(gatewayId, "Gateway " + gatewayId, 
"Sharing domain for " + gatewayId));
+
+        // Entity types
+        String[] entityTypes = {"PROJECT", "EXPERIMENT", "FILE", 
"APPLICATION_DEPLOYMENT", "GROUP_RESOURCE_PROFILE", "CREDENTIAL_TOKEN"};
+        for (String et : entityTypes) {
+            tryCreate("entity type " + et, () ->
+                    sharingFacade.createEntityType(gatewayId + ":" + et, 
gatewayId, et, et + " entity type"));
+        }
+
+        // Permission types
+        String[] permTypes = {"READ", "WRITE", "MANAGE_SHARING"};
+        for (String pt : permTypes) {
+            tryCreate("permission type " + pt, () ->
+                    sharingFacade.createPermissionType(gatewayId + ":" + pt, 
gatewayId, pt, pt + " permission type"));
+        }
+
+        logger.info("Sharing initialized for gateway: {}", gatewayId);
+    }
+
+    private void tryCreate(String label, ThrowingRunnable action) {
+        try {
+            action.run();
+            logger.info("Created {}", label);
+        } catch (Exception e) {
+            logger.debug("{} already exists: {}", label, e.getMessage());
+        }
+    }
+
+    @FunctionalInterface
+    private interface ThrowingRunnable {
+        void run() throws Exception;
+    }
+
     @Override
+    @jakarta.annotation.PostConstruct
     public void postInit() {
 
         try {
@@ -71,8 +116,12 @@ public class ExpCatalogDBInitConfig implements DBInitConfig 
{
                         
.setOauthClientSecret(ServerSettings.getSetting("default.registry.oauth.client.secret"))
                         .build();
                 gatewayRegistry.addGateway(gateway);
+                logger.info("Created default gateway: {}", defaultGatewayId);
             }
 
+            // Initialize sharing domain, entity types, and permission types 
(idempotent)
+            initializeSharingForGateway(defaultGatewayId);
+
             String defaultUsername = ServerSettings.getDefaultUser();
             if (!gatewayRegistry.isUserExists(defaultGatewayId, 
defaultUsername)) {
                 UserProfile defaultUser = UserProfile.newBuilder()
diff --git 
a/airavata-api/research-service/src/main/java/org/apache/airavata/research/mapper/ResearchMapper.java
 
b/airavata-api/research-service/src/main/java/org/apache/airavata/research/mapper/ResearchMapper.java
index fa6efbf120..c75db099b5 100644
--- 
a/airavata-api/research-service/src/main/java/org/apache/airavata/research/mapper/ResearchMapper.java
+++ 
b/airavata-api/research-service/src/main/java/org/apache/airavata/research/mapper/ResearchMapper.java
@@ -109,8 +109,10 @@ public interface ResearchMapper extends 
CommonMapperConversions {
     }
 
     // --- Project ---
+    @Mapping(target = "projectId", source = "projectID")
     Project projectToModel(ProjectEntity entity);
 
+    @Mapping(target = "projectID", source = "projectId")
     ProjectEntity projectToEntity(Project model);
 
     // --- Notification ---
diff --git 
a/airavata-api/sharing-service/src/main/java/org/apache/airavata/sharing/repository/EntityRepository.java
 
b/airavata-api/sharing-service/src/main/java/org/apache/airavata/sharing/repository/EntityRepository.java
index d59d617980..44436b77bf 100644
--- 
a/airavata-api/sharing-service/src/main/java/org/apache/airavata/sharing/repository/EntityRepository.java
+++ 
b/airavata-api/sharing-service/src/main/java/org/apache/airavata/sharing/repository/EntityRepository.java
@@ -155,10 +155,10 @@ public class EntityRepository extends 
AbstractRepository<EntityEntity, EntityPK>
             entity.setDescription((String) (rs[6]));
             entity.setBinaryData((byte[]) (rs[7]));
             entity.setFullText((String) (rs[8]));
-            entity.setSharedCount((long) rs[9]);
-            entity.setOriginalEntityCreationTime((long) (rs[10]));
-            entity.setCreatedTime((long) (rs[11]));
-            entity.setUpdatedTime((long) (rs[12]));
+            entity.setSharedCount(rs[9] != null ? ((Number) rs[9]).longValue() 
: 0L);
+            entity.setOriginalEntityCreationTime(rs[10] != null ? ((Number) 
rs[10]).longValue() : 0L);
+            entity.setCreatedTime(rs[11] != null ? ((Number) 
rs[11]).longValue() : 0L);
+            entity.setUpdatedTime(rs[12] != null ? ((Number) 
rs[12]).longValue() : 0L);
 
             // Removing duplicates. Another option is to change the query to 
remove duplicates.
             if (!keys.containsKey(entity + domainId + "," + 
entity.getEntityId())) {
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/grpc/GrpcStatusMapper.java 
b/airavata-api/src/main/java/org/apache/airavata/grpc/GrpcStatusMapper.java
index 7b19883b1f..5dee614d2b 100644
--- a/airavata-api/src/main/java/org/apache/airavata/grpc/GrpcStatusMapper.java
+++ b/airavata-api/src/main/java/org/apache/airavata/grpc/GrpcStatusMapper.java
@@ -26,9 +26,15 @@ public final class GrpcStatusMapper {
 
     private GrpcStatusMapper() {}
 
+    private static final int MAX_MESSAGE_LENGTH = 1024;
+
     public static StatusRuntimeException toStatusException(Exception e) {
         String className = e.getClass().getSimpleName();
-        String message = e.getMessage() != null ? e.getMessage() : className;
+        String fullMessage = e.getMessage() != null ? e.getMessage() : 
className;
+        // Truncate to avoid exceeding gRPC metadata size limits
+        String message = fullMessage.length() > MAX_MESSAGE_LENGTH
+                ? fullMessage.substring(0, MAX_MESSAGE_LENGTH) + "... 
(truncated)"
+                : fullMessage;
 
         Status status =
                 switch (className) {
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/task/AdaptorSupportImpl.java 
b/airavata-api/src/main/java/org/apache/airavata/task/AdaptorSupportImpl.java
index 07cabc923e..383032d029 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/task/AdaptorSupportImpl.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/task/AdaptorSupportImpl.java
@@ -39,7 +39,7 @@ public class AdaptorSupportImpl implements AdaptorSupport {
     private static final Logger logger = 
LoggerFactory.getLogger(AdaptorSupportImpl.class);
 
     private static final String SSHJ_AGENT_ADAPTOR_CLASS = 
"org.apache.airavata.compute.util.SSHJAgentAdaptor";
-    private static final String SSHJ_STORAGE_ADAPTOR_CLASS = 
"org.apache.airavata.storage.util.SSHJStorageAdaptor";
+    private static final String SSHJ_STORAGE_ADAPTOR_CLASS = 
"org.apache.airavata.compute.util.SSHJStorageAdaptor";
 
     private static AdaptorSupportImpl INSTANCE;
 
diff --git 
a/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/grpc/UserStorageGrpcService.java
 
b/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/grpc/UserStorageGrpcService.java
index 673a01c775..ea70d79a31 100644
--- 
a/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/grpc/UserStorageGrpcService.java
+++ 
b/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/grpc/UserStorageGrpcService.java
@@ -62,17 +62,74 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
         this.gatewayStoragePreferenceProvider = 
gatewayStoragePreferenceProvider;
     }
 
+    private StoragePreference resolveStoragePreference(String 
storageResourceId) throws Exception {
+        RequestContext ctx = GrpcRequestContext.current();
+        var prefs = 
gatewayStoragePreferenceProvider.getAllGatewayStoragePreferences(ctx.getGatewayId());
+        if (prefs == null || prefs.isEmpty()) {
+            return null;
+        }
+        String resolvedId = (storageResourceId != null && 
!storageResourceId.isEmpty())
+                ? storageResourceId : prefs.get(0).getStorageResourceId();
+        for (var pref : prefs) {
+            if (pref.getStorageResourceId().equals(resolvedId)) {
+                return pref;
+            }
+        }
+        return prefs.get(0);
+    }
+
     private StorageResourceAdaptor getStorageAdaptor(String storageResourceId) 
throws Exception {
         RequestContext ctx = GrpcRequestContext.current();
+        String resolvedId = storageResourceId;
+        String credentialToken = ctx.getAccessToken(); // fallback
+        String loginUser = ctx.getUserId(); // fallback
+
+        // Resolve storage resource, credential, and login user from gateway 
preferences
+        StoragePreference pref = resolveStoragePreference(storageResourceId);
+        if (pref != null) {
+            if (resolvedId == null || resolvedId.isEmpty()) {
+                resolvedId = pref.getStorageResourceId();
+            }
+            String csToken = pref.getResourceSpecificCredentialStoreToken();
+            if (csToken != null && !csToken.isEmpty()) {
+                credentialToken = csToken;
+            }
+            String prefUser = pref.getLoginUserName();
+            if (prefUser != null && !prefUser.isEmpty()) {
+                loginUser = prefUser;
+            }
+        }
+        if (resolvedId == null || resolvedId.isEmpty()) {
+            throw new IllegalStateException("No storage resource configured 
for gateway " + ctx.getGatewayId());
+        }
         return adaptorSupport.fetchStorageAdaptor(
-                ctx.getGatewayId(), storageResourceId, 
DataMovementProtocol.SCP, ctx.getAccessToken(), ctx.getUserId());
+                ctx.getGatewayId(), resolvedId, DataMovementProtocol.SCP, 
credentialToken, loginUser);
+    }
+
+    /**
+     * Resolve paths like "~/" or "~" to the storage preference's 
fileSystemRootLocation.
+     * SFTP doesn't support shell tilde expansion.
+     */
+    private String resolvePath(String path, String storageResourceId) throws 
Exception {
+        if (path == null || path.isEmpty()) {
+            path = "/";
+        }
+        if (path.startsWith("~/") || path.equals("~")) {
+            StoragePreference pref = 
resolveStoragePreference(storageResourceId);
+            String root = (pref != null && 
!pref.getFileSystemRootLocation().isEmpty())
+                    ? pref.getFileSystemRootLocation() : "/";
+            if (!root.endsWith("/")) root += "/";
+            String suffix = path.length() > 2 ? path.substring(2) : "";
+            path = root + suffix;
+        }
+        return path;
     }
 
     @Override
     public void uploadFile(UploadFileRequest request, 
StreamObserver<DataProductModel> observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            String remotePath = request.getPath();
+            String remotePath = resolvePath(request.getPath(), 
request.getStorageResourceId());
 
             FileMetadata metadata = new FileMetadata();
             metadata.setName(request.getName());
@@ -101,7 +158,7 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void downloadFile(DownloadFileRequest request, 
StreamObserver<DownloadFileResponse> observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            String remotePath = request.getPath();
+            String remotePath = resolvePath(request.getPath(), 
request.getStorageResourceId());
 
             FileMetadata metadata = adaptor.getFileMetadata(remotePath);
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@@ -121,7 +178,8 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void fileExists(FileExistsRequest request, 
StreamObserver<FileExistsResponse> observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            boolean exists = adaptor.doesFileExist(request.getPath());
+            String path = resolvePath(request.getPath(), 
request.getStorageResourceId());
+            boolean exists = adaptor.doesFileExist(path);
             
observer.onNext(FileExistsResponse.newBuilder().setExists(exists).build());
             observer.onCompleted();
         } catch (Exception e) {
@@ -133,10 +191,11 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void dirExists(DirExistsRequest request, 
StreamObserver<DirExistsResponse> observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
+            String path = resolvePath(request.getPath(), 
request.getStorageResourceId());
             // Check existence via getFileMetadata — if it's a directory, it 
exists
             boolean exists = false;
             try {
-                FileMetadata metadata = 
adaptor.getFileMetadata(request.getPath());
+                FileMetadata metadata = adaptor.getFileMetadata(path);
                 exists = metadata.isDirectory();
             } catch (Exception ignored) {
                 // Path does not exist or is not accessible
@@ -152,7 +211,7 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void listDir(ListDirRequest request, 
StreamObserver<ListDirResponse> observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            String path = request.getPath();
+            String path = resolvePath(request.getPath(), 
request.getStorageResourceId());
 
             List<String> entries = adaptor.listDirectory(path);
             ListDirResponse.Builder responseBuilder = 
ListDirResponse.newBuilder();
@@ -179,7 +238,8 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void deleteFile(DeleteFileRequest request, StreamObserver<Empty> 
observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            adaptor.executeCommand("rm -f " + request.getPath(), "/");
+            String path = resolvePath(request.getPath(), 
request.getStorageResourceId());
+            adaptor.executeCommand("rm -f " + path, "/");
             observer.onNext(Empty.getDefaultInstance());
             observer.onCompleted();
         } catch (Exception e) {
@@ -191,7 +251,8 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void deleteDir(DeleteDirRequest request, StreamObserver<Empty> 
observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            adaptor.deleteDirectory(request.getPath());
+            String path = resolvePath(request.getPath(), 
request.getStorageResourceId());
+            adaptor.deleteDirectory(path);
             observer.onNext(Empty.getDefaultInstance());
             observer.onCompleted();
         } catch (Exception e) {
@@ -203,7 +264,9 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void moveFile(MoveFileRequest request, 
StreamObserver<DataProductModel> observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            adaptor.executeCommand("mv " + request.getSourcePath() + " " + 
request.getDestinationPath(), "/");
+            String src = resolvePath(request.getSourcePath(), 
request.getStorageResourceId());
+            String dst = resolvePath(request.getDestinationPath(), 
request.getStorageResourceId());
+            adaptor.executeCommand("mv " + src + " " + dst, "/");
 
             DataProductModel product = DataProductModel.newBuilder()
                     .setProductName(Paths.get(request.getDestinationPath())
@@ -221,7 +284,8 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void createDir(CreateDirRequest request, 
StreamObserver<CreateDirResponse> observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            adaptor.createDirectory(request.getPath(), true);
+            String path = resolvePath(request.getPath(), 
request.getStorageResourceId());
+            adaptor.createDirectory(path, true);
             observer.onNext(CreateDirResponse.newBuilder()
                     .setCreatedPath(request.getPath())
                     .build());
@@ -235,7 +299,9 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void createSymlink(CreateSymlinkRequest request, 
StreamObserver<Empty> observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            adaptor.executeCommand("ln -s " + request.getTargetPath() + " " + 
request.getSourcePath(), "/");
+            String target = resolvePath(request.getTargetPath(), 
request.getStorageResourceId());
+            String source = resolvePath(request.getSourcePath(), 
request.getStorageResourceId());
+            adaptor.executeCommand("ln -s " + target + " " + source, "/");
             observer.onNext(Empty.getDefaultInstance());
             observer.onCompleted();
         } catch (Exception e) {
@@ -247,8 +313,9 @@ public class UserStorageGrpcService extends 
UserStorageServiceGrpc.UserStorageSe
     public void getFileMetadata(GetFileMetadataRequest request, 
StreamObserver<FileMetadataResponse> observer) {
         try {
             StorageResourceAdaptor adaptor = 
getStorageAdaptor(request.getStorageResourceId());
-            FileMetadata meta = adaptor.getFileMetadata(request.getPath());
-            observer.onNext(toFileMetadataResponse(meta, request.getPath()));
+            String path = resolvePath(request.getPath(), 
request.getStorageResourceId());
+            FileMetadata meta = adaptor.getFileMetadata(path);
+            observer.onNext(toFileMetadataResponse(meta, path));
             observer.onCompleted();
         } catch (Exception e) {
             observer.onError(GrpcStatusMapper.toStatusException(e));
diff --git 
a/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/mapper/StorageMapper.java
 
b/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/mapper/StorageMapper.java
index be60067e9d..89f0ded81f 100644
--- 
a/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/mapper/StorageMapper.java
+++ 
b/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/mapper/StorageMapper.java
@@ -44,7 +44,23 @@ public interface StorageMapper extends 
CommonMapperConversions {
     StorageMapper INSTANCE = Mappers.getMapper(StorageMapper.class);
 
     // --- StorageResourceDescription ---
-    StorageResourceDescription storageResourceToModel(StorageResourceEntity 
entity);
+    // Manual mapping because MapStruct can't populate protobuf repeated 
fields (addAll*)
+    default StorageResourceDescription 
storageResourceToModel(StorageResourceEntity entity) {
+        if (entity == null) return null;
+        StorageResourceDescription.Builder b = 
StorageResourceDescription.newBuilder();
+        if (entity.getStorageResourceId() != null) 
b.setStorageResourceId(entity.getStorageResourceId());
+        if (entity.getHostName() != null) b.setHostName(entity.getHostName());
+        if (entity.getStorageResourceDescription() != null) 
b.setStorageResourceDescription(entity.getStorageResourceDescription());
+        b.setEnabled(entity.isEnabled());
+        if (entity.getCreationTime() != null) 
b.setCreationTime(entity.getCreationTime().getTime());
+        if (entity.getUpdateTime() != null) 
b.setUpdateTime(entity.getUpdateTime().getTime());
+        if (entity.getDataMovementInterfaces() != null) {
+            for (DataMovementInterfaceEntity dm : 
entity.getDataMovementInterfaces()) {
+                b.addDataMovementInterfaces(dataMovementInterfaceToModel(dm));
+            }
+        }
+        return b.build();
+    }
 
     StorageResourceEntity storageResourceToEntity(StorageResourceDescription 
model);
 
diff --git 
a/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/model/DataMovementInterfaceEntity.java
 
b/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/model/DataMovementInterfaceEntity.java
index 6913d9b78c..efadb81791 100644
--- 
a/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/model/DataMovementInterfaceEntity.java
+++ 
b/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/model/DataMovementInterfaceEntity.java
@@ -28,12 +28,12 @@ import 
org.apache.airavata.model.data.movement.proto.DataMovementProtocol;
  * The persistent class for the data_movement_interface database table.
  */
 @Entity
-@Table(name = "DATA_MOVEMENT_INTERFACE")
+@Table(name = "STORAGE_INTERFACE")
 @IdClass(DataMovementInterfacePK.class)
 public class DataMovementInterfaceEntity implements Serializable {
     private static final long serialVersionUID = 1L;
 
-    @Column(name = "RESOURCE_ID")
+    @Column(name = "STORAGE_RESOURCE_ID")
     @Id
     private String resourceId;
 
diff --git a/airavata-python-sdk/airavata_sdk/client.py 
b/airavata-python-sdk/airavata_sdk/client.py
index 632cbe521c..3d2dcdf708 100644
--- a/airavata-python-sdk/airavata_sdk/client.py
+++ b/airavata-python-sdk/airavata_sdk/client.py
@@ -16,10 +16,13 @@ class AiravataClient:
 
     def __init__(self, host, port, token, gateway_id, secure=False, 
claims=None):
         target = f"{host}:{port}"
+        options = [
+            ("grpc.max_metadata_size", 64 * 1024),  # 64KB for large error 
messages
+        ]
         if secure:
-            self._channel = grpc.secure_channel(target, 
grpc.ssl_channel_credentials())
+            self._channel = grpc.secure_channel(target, 
grpc.ssl_channel_credentials(), options=options)
         else:
-            self._channel = grpc.insecure_channel(target)
+            self._channel = grpc.insecure_channel(target, options=options)
 
         self._metadata = []
         if token:
diff --git a/airavata-python-sdk/airavata_sdk/facade/compute.py 
b/airavata-python-sdk/airavata_sdk/facade/compute.py
index 5ecc2dbe8f..a4c7a93aa7 100644
--- a/airavata-python-sdk/airavata_sdk/facade/compute.py
+++ b/airavata-python-sdk/airavata_sdk/facade/compute.py
@@ -29,10 +29,11 @@ class ComputeClient:
 
     def register_compute_resource(self, compute_resource):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.RegisterComputeResource(
+        response = self._resource.RegisterComputeResource(
             
pb2.RegisterComputeResourceRequest(compute_resource=compute_resource),
             metadata=self._metadata,
         )
+        return response.compute_resource_id
 
     def get_compute_resource(self, compute_resource_id):
         pb2 = self._svc("resource_service_pb2")
@@ -43,10 +44,11 @@ class ComputeClient:
 
     def get_all_compute_resource_names(self):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.GetAllComputeResourceNames(
+        response = self._resource.GetAllComputeResourceNames(
             pb2.GetAllComputeResourceNamesRequest(),
             metadata=self._metadata,
         )
+        return dict(response.compute_resource_names)
 
     def update_compute_resource(self, compute_resource_id, compute_resource):
         pb2 = self._svc("resource_service_pb2")
@@ -66,10 +68,11 @@ class ComputeClient:
 
     def add_local_submission_details(self, compute_resource_id, priority, 
local_submission):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.AddLocalSubmission(
+        response = self._resource.AddLocalSubmission(
             
pb2.AddLocalSubmissionRequest(compute_resource_id=compute_resource_id, 
priority=priority, local_submission=local_submission),
             metadata=self._metadata,
         )
+        return response.submission_id
 
     def update_local_submission_details(self, submission_id, local_submission):
         pb2 = self._svc("resource_service_pb2")
@@ -87,17 +90,19 @@ class ComputeClient:
 
     def add_ssh_job_submission_details(self, compute_resource_id, priority, 
ssh_job_submission):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.AddSSHJobSubmission(
+        response = self._resource.AddSSHJobSubmission(
             
pb2.AddSSHJobSubmissionRequest(compute_resource_id=compute_resource_id, 
priority=priority, ssh_job_submission=ssh_job_submission),
             metadata=self._metadata,
         )
+        return response.submission_id
 
     def add_ssh_fork_job_submission_details(self, compute_resource_id, 
priority, ssh_job_submission):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.AddSSHForkJobSubmission(
+        response = self._resource.AddSSHForkJobSubmission(
             
pb2.AddSSHForkJobSubmissionRequest(compute_resource_id=compute_resource_id, 
priority=priority, ssh_job_submission=ssh_job_submission),
             metadata=self._metadata,
         )
+        return response.submission_id
 
     def get_ssh_job_submission(self, submission_id):
         pb2 = self._svc("resource_service_pb2")
@@ -108,10 +113,11 @@ class ComputeClient:
 
     def add_unicore_job_submission_details(self, compute_resource_id, 
priority, unicore_job_submission):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.AddUnicoreJobSubmission(
+        response = self._resource.AddUnicoreJobSubmission(
             
pb2.AddUnicoreJobSubmissionRequest(compute_resource_id=compute_resource_id, 
priority=priority, unicore_job_submission=unicore_job_submission),
             metadata=self._metadata,
         )
+        return response.submission_id
 
     def get_unicore_job_submission(self, submission_id):
         pb2 = self._svc("resource_service_pb2")
@@ -122,10 +128,11 @@ class ComputeClient:
 
     def add_cloud_job_submission_details(self, compute_resource_id, priority, 
cloud_job_submission):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.AddCloudJobSubmission(
+        response = self._resource.AddCloudJobSubmission(
             
pb2.AddCloudJobSubmissionRequest(compute_resource_id=compute_resource_id, 
priority=priority, cloud_job_submission=cloud_job_submission),
             metadata=self._metadata,
         )
+        return response.submission_id
 
     def get_cloud_job_submission(self, submission_id):
         pb2 = self._svc("resource_service_pb2")
@@ -175,10 +182,11 @@ class ComputeClient:
 
     def register_gateway_resource_profile(self, gateway_resource_profile):
         pb2 = self._svc("gateway_resource_profile_service_pb2")
-        return self._gw_profile.RegisterGatewayResourceProfile(
+        response = self._gw_profile.RegisterGatewayResourceProfile(
             
pb2.RegisterGatewayResourceProfileRequest(gateway_resource_profile=gateway_resource_profile),
             metadata=self._metadata,
         )
+        return response.gateway_id
 
     def get_gateway_resource_profile(self, gateway_id):
         pb2 = self._svc("gateway_resource_profile_service_pb2")
@@ -231,24 +239,27 @@ class ComputeClient:
 
     def get_all_gateway_compute_resource_preferences(self, gateway_id):
         pb2 = self._svc("gateway_resource_profile_service_pb2")
-        return self._gw_profile.GetAllComputePreferences(
+        response = self._gw_profile.GetAllComputePreferences(
             pb2.GetAllComputePreferencesRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.compute_resource_preferences)
 
     def get_all_gateway_storage_preferences(self, gateway_id):
         pb2 = self._svc("gateway_resource_profile_service_pb2")
-        return self._gw_profile.GetAllStoragePreferences(
+        response = self._gw_profile.GetAllStoragePreferences(
             pb2.GetAllStoragePreferencesRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.storage_preferences)
 
     def get_all_gateway_resource_profiles(self):
         pb2 = self._svc("gateway_resource_profile_service_pb2")
-        return self._gw_profile.GetAllGatewayResourceProfiles(
+        response = self._gw_profile.GetAllGatewayResourceProfiles(
             pb2.GetAllGatewayResourceProfilesRequest(),
             metadata=self._metadata,
         )
+        return list(response.gateway_resource_profiles)
 
     def update_gateway_compute_resource_preference(self, gateway_id, 
compute_resource_id, compute_resource_preference):
         pb2 = self._svc("gateway_resource_profile_service_pb2")
@@ -280,10 +291,11 @@ class ComputeClient:
 
     def get_ssh_account_provisioners(self):
         pb2 = self._svc("gateway_resource_profile_service_pb2")
-        return self._gw_profile.GetSSHAccountProvisioners(
+        response = self._gw_profile.GetSSHAccountProvisioners(
             pb2.GetSSHAccountProvisionersRequest(),
             metadata=self._metadata,
         )
+        return list(response.ssh_account_provisioners)
 
     # ================================================================
     # Group Resource Profile Service
@@ -319,10 +331,11 @@ class ComputeClient:
 
     def get_group_resource_list(self):
         pb2 = self._svc("group_resource_profile_service_pb2")
-        return self._grp_profile.GetGroupResourceList(
+        response = self._grp_profile.GetGroupResourceList(
             pb2.GetGroupResourceListRequest(),
             metadata=self._metadata,
         )
+        return list(response.group_resource_profiles)
 
     def remove_group_compute_prefs(self, group_resource_profile_id, 
compute_resource_id):
         pb2 = self._svc("group_resource_profile_service_pb2")
@@ -368,24 +381,27 @@ class ComputeClient:
 
     def get_group_compute_resource_pref_list(self, group_resource_profile_id):
         pb2 = self._svc("group_resource_profile_service_pb2")
-        return self._grp_profile.GetGroupComputePrefList(
+        response = self._grp_profile.GetGroupComputePrefList(
             
pb2.GetGroupComputePrefListRequest(group_resource_profile_id=group_resource_profile_id),
             metadata=self._metadata,
         )
+        return list(response.group_compute_resource_preferences)
 
     def get_group_batch_queue_resource_policy_list(self, 
group_resource_profile_id):
         pb2 = self._svc("group_resource_profile_service_pb2")
-        return self._grp_profile.GetGroupBatchQueuePolicyList(
+        response = self._grp_profile.GetGroupBatchQueuePolicyList(
             
pb2.GetGroupBatchQueuePolicyListRequest(group_resource_profile_id=group_resource_profile_id),
             metadata=self._metadata,
         )
+        return list(response.batch_queue_resource_policies)
 
     def get_group_compute_resource_policy_list(self, 
group_resource_profile_id):
         pb2 = self._svc("group_resource_profile_service_pb2")
-        return self._grp_profile.GetGroupComputeResourcePolicyList(
+        response = self._grp_profile.GetGroupComputeResourcePolicyList(
             
pb2.GetGroupComputeResourcePolicyListRequest(group_resource_profile_id=group_resource_profile_id),
             metadata=self._metadata,
         )
+        return list(response.compute_resource_policies)
 
     def get_gateway_groups(self):
         pb2 = self._svc("group_resource_profile_service_pb2")
@@ -400,17 +416,19 @@ class ComputeClient:
 
     def register_user_resource_profile(self, user_resource_profile):
         pb2 = self._svc("user_resource_profile_service_pb2")
-        return self._user_profile.RegisterUserResourceProfile(
+        response = self._user_profile.RegisterUserResourceProfile(
             
pb2.RegisterUserResourceProfileRequest(user_resource_profile=user_resource_profile),
             metadata=self._metadata,
         )
+        return response.user_id
 
     def is_user_resource_profile_exists(self, user_id, gateway_id):
         pb2 = self._svc("user_resource_profile_service_pb2")
-        return self._user_profile.IsUserResourceProfileExists(
+        response = self._user_profile.IsUserResourceProfileExists(
             pb2.IsUserResourceProfileExistsRequest(user_id=user_id, 
gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return response.exists
 
     def get_user_resource_profile(self, user_id, gateway_id):
         pb2 = self._svc("user_resource_profile_service_pb2")
@@ -463,24 +481,27 @@ class ComputeClient:
 
     def get_all_user_compute_resource_preferences(self, user_id, gateway_id):
         pb2 = self._svc("user_resource_profile_service_pb2")
-        return self._user_profile.GetAllUserComputePreferences(
+        response = self._user_profile.GetAllUserComputePreferences(
             pb2.GetAllUserComputePreferencesRequest(user_id=user_id, 
gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.user_compute_resource_preferences)
 
     def get_all_user_storage_preferences(self, user_id, gateway_id):
         pb2 = self._svc("user_resource_profile_service_pb2")
-        return self._user_profile.GetAllUserStoragePreferences(
+        response = self._user_profile.GetAllUserStoragePreferences(
             pb2.GetAllUserStoragePreferencesRequest(user_id=user_id, 
gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.user_storage_preferences)
 
     def get_all_user_resource_profiles(self):
         pb2 = self._svc("user_resource_profile_service_pb2")
-        return self._user_profile.GetAllUserResourceProfiles(
+        response = self._user_profile.GetAllUserResourceProfiles(
             pb2.GetAllUserResourceProfilesRequest(),
             metadata=self._metadata,
         )
+        return list(response.user_resource_profiles)
 
     def update_user_compute_resource_preference(self, user_id, gateway_id, 
compute_resource_id, user_compute_resource_preference):
         pb2 = self._svc("user_resource_profile_service_pb2")
@@ -512,7 +533,8 @@ class ComputeClient:
 
     def get_latest_queue_statuses(self):
         pb2 = self._svc("user_resource_profile_service_pb2")
-        return self._user_profile.GetLatestQueueStatuses(
+        response = self._user_profile.GetLatestQueueStatuses(
             pb2.GetLatestQueueStatusesRequest(),
             metadata=self._metadata,
         )
+        return list(response.queue_statuses)
diff --git a/airavata-python-sdk/airavata_sdk/facade/credential.py 
b/airavata-python-sdk/airavata_sdk/facade/credential.py
index b2a9628f4f..bdaf7a4f19 100644
--- a/airavata-python-sdk/airavata_sdk/facade/credential.py
+++ b/airavata-python-sdk/airavata_sdk/facade/credential.py
@@ -24,17 +24,19 @@ class CredentialClient:
 
     def generate_and_register_ssh_keys(self, gateway_id, username, 
description=""):
         pb2 = self._svc("credential_service_pb2")
-        return self._stub.GenerateAndRegisterSSHKeys(
+        response = self._stub.GenerateAndRegisterSSHKeys(
             pb2.GenerateAndRegisterSSHKeysRequest(gateway_id=gateway_id, 
username=username, description=description),
             metadata=self._metadata,
         )
+        return response.token
 
     def register_pwd_credential(self, gateway_id, password_credential):
         pb2 = self._svc("credential_service_pb2")
-        return self._stub.RegisterPwdCredential(
+        response = self._stub.RegisterPwdCredential(
             pb2.RegisterPwdCredentialRequest(gateway_id=gateway_id, 
password_credential=password_credential),
             metadata=self._metadata,
         )
+        return response.token
 
     def get_credential_summary(self, token_id, gateway_id):
         pb2 = self._svc("credential_service_pb2")
@@ -45,10 +47,11 @@ class CredentialClient:
 
     def get_all_credential_summaries(self, gateway_id, type):
         pb2 = self._svc("credential_service_pb2")
-        return self._stub.GetAllCredentialSummaries(
+        response = self._stub.GetAllCredentialSummaries(
             pb2.GetAllCredentialSummariesRequest(gateway_id=gateway_id, 
type=type),
             metadata=self._metadata,
         )
+        return list(response.credential_summaries)
 
     def delete_ssh_pub_key(self, token_id, gateway_id):
         pb2 = self._svc("credential_service_pb2")
@@ -66,21 +69,24 @@ class CredentialClient:
 
     def is_ssh_setup_complete(self, compute_resource_id, gateway_id, username):
         pb2 = self._svc("credential_service_pb2")
-        return self._stub.IsSSHSetupComplete(
+        response = self._stub.IsSSHSetupComplete(
             
pb2.IsSSHSetupCompleteRequest(compute_resource_id=compute_resource_id, 
gateway_id=gateway_id, username=username),
             metadata=self._metadata,
         )
+        return response.is_complete
 
     def setup_ssh_account(self, compute_resource_id, gateway_id, username):
         pb2 = self._svc("credential_service_pb2")
-        return self._stub.SetupSSHAccount(
+        response = self._stub.SetupSSHAccount(
             
pb2.SetupSSHAccountRequest(compute_resource_id=compute_resource_id, 
gateway_id=gateway_id, username=username),
             metadata=self._metadata,
         )
+        return response.success
 
     def does_user_have_ssh_account(self, compute_resource_id, gateway_id, 
username):
         pb2 = self._svc("credential_service_pb2")
-        return self._stub.DoesUserHaveSSHAccount(
+        response = self._stub.DoesUserHaveSSHAccount(
             
pb2.DoesUserHaveSSHAccountRequest(compute_resource_id=compute_resource_id, 
gateway_id=gateway_id, username=username),
             metadata=self._metadata,
         )
+        return response.has_account
diff --git a/airavata-python-sdk/airavata_sdk/facade/iam.py 
b/airavata-python-sdk/airavata_sdk/facade/iam.py
index 97f4c14bf4..71d2341986 100644
--- a/airavata-python-sdk/airavata_sdk/facade/iam.py
+++ b/airavata-python-sdk/airavata_sdk/facade/iam.py
@@ -27,24 +27,27 @@ class IamClient:
 
     def is_user_exists(self, gateway_id, user_name):
         pb2 = self._svc("gateway_service_pb2")
-        return self._gateway.IsUserExists(
+        response = self._gateway.IsUserExists(
             pb2.IsUserExistsRequest(gateway_id=gateway_id, 
user_name=user_name),
             metadata=self._metadata,
         )
+        return response.exists
 
     def add_gateway(self, gateway):
         pb2 = self._svc("gateway_service_pb2")
-        return self._gateway.AddGateway(
+        response = self._gateway.AddGateway(
             pb2.AddGatewayRequest(gateway=gateway),
             metadata=self._metadata,
         )
+        return response.gateway_id
 
     def get_all_users_in_gateway(self, gateway_id):
         pb2 = self._svc("gateway_service_pb2")
-        return self._gateway.GetAllUsersInGateway(
+        response = self._gateway.GetAllUsersInGateway(
             pb2.GetAllUsersInGatewayRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.user_names)
 
     def update_gateway(self, gateway_id, gateway):
         pb2 = self._svc("gateway_service_pb2")
@@ -69,17 +72,19 @@ class IamClient:
 
     def get_all_gateways(self):
         pb2 = self._svc("gateway_service_pb2")
-        return self._gateway.GetAllGateways(
+        response = self._gateway.GetAllGateways(
             pb2.GetAllGatewaysRequest(),
             metadata=self._metadata,
         )
+        return list(response.gateways)
 
     def is_gateway_exist(self, gateway_id):
         pb2 = self._svc("gateway_service_pb2")
-        return self._gateway.IsGatewayExist(
+        response = self._gateway.IsGatewayExist(
             pb2.IsGatewayExistRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return response.exists
 
     # ================================================================
     # IAM Admin Service
diff --git a/airavata-python-sdk/airavata_sdk/facade/research.py 
b/airavata-python-sdk/airavata_sdk/facade/research.py
index 706bcae4f3..fd610e7a37 100644
--- a/airavata-python-sdk/airavata_sdk/facade/research.py
+++ b/airavata-python-sdk/airavata_sdk/facade/research.py
@@ -46,10 +46,11 @@ class ResearchClient:
 
     def search_experiments(self, gateway_id, user_name, filters=None, 
limit=-1, offset=0):
         pb2 = self._svc("experiment_service_pb2")
-        return self._experiment.SearchExperiments(
+        response = self._experiment.SearchExperiments(
             pb2.SearchExperimentsRequest(gateway_id=gateway_id, 
user_name=user_name, filters=filters or {}, limit=limit, offset=offset),
             metadata=self._metadata,
         )
+        return list(response.experiments)
 
     def get_experiment_statistics(self, gateway_id, from_time, to_time, 
user_name="", application_name="", resource_host_name="", limit=0, offset=0):
         pb2 = self._svc("experiment_service_pb2")
@@ -64,24 +65,27 @@ class ResearchClient:
 
     def get_experiments_in_project(self, project_id, limit=-1, offset=0):
         pb2 = self._svc("experiment_service_pb2")
-        return self._experiment.GetExperimentsInProject(
+        response = self._experiment.GetExperimentsInProject(
             pb2.GetExperimentsInProjectRequest(project_id=project_id, 
limit=limit, offset=offset),
             metadata=self._metadata,
         )
+        return list(response.experiments)
 
     def get_user_experiments(self, gateway_id, user_name, limit=-1, offset=0):
         pb2 = self._svc("experiment_service_pb2")
-        return self._experiment.GetUserExperiments(
+        response = self._experiment.GetUserExperiments(
             pb2.GetUserExperimentsRequest(gateway_id=gateway_id, 
user_name=user_name, limit=limit, offset=offset),
             metadata=self._metadata,
         )
+        return list(response.experiments)
 
     def create_experiment(self, gateway_id, experiment):
         pb2 = self._svc("experiment_service_pb2")
-        return self._experiment.CreateExperiment(
+        response = self._experiment.CreateExperiment(
             pb2.CreateExperimentRequest(gateway_id=gateway_id, 
experiment=experiment),
             metadata=self._metadata,
         )
+        return response.experiment_id
 
     def delete_experiment(self, experiment_id):
         pb2 = self._svc("experiment_service_pb2")
@@ -155,10 +159,11 @@ class ResearchClient:
 
     def get_experiment_outputs(self, experiment_id):
         pb2 = self._svc("experiment_service_pb2")
-        return self._experiment.GetExperimentOutputs(
+        response = self._experiment.GetExperimentOutputs(
             pb2.GetExperimentOutputsRequest(experiment_id=experiment_id),
             metadata=self._metadata,
         )
+        return list(response.outputs)
 
     def get_intermediate_outputs(self, experiment_id, output_names=None):
         pb2 = self._svc("experiment_service_pb2")
@@ -183,14 +188,15 @@ class ResearchClient:
 
     def get_job_details(self, experiment_id):
         pb2 = self._svc("experiment_service_pb2")
-        return self._experiment.GetJobDetails(
+        response = self._experiment.GetJobDetails(
             pb2.GetJobDetailsRequest(experiment_id=experiment_id),
             metadata=self._metadata,
         )
+        return list(response.jobs)
 
     def clone_experiment(self, experiment_id, new_experiment_name="", 
new_experiment_project_id=""):
         pb2 = self._svc("experiment_service_pb2")
-        return self._experiment.CloneExperiment(
+        response = self._experiment.CloneExperiment(
             pb2.CloneExperimentRequest(
                 experiment_id=experiment_id,
                 new_experiment_name=new_experiment_name,
@@ -198,10 +204,11 @@ class ResearchClient:
             ),
             metadata=self._metadata,
         )
+        return response.experiment_id
 
     def clone_experiment_by_admin(self, experiment_id, new_experiment_name="", 
new_experiment_project_id=""):
         pb2 = self._svc("experiment_service_pb2")
-        return self._experiment.CloneExperiment(
+        response = self._experiment.CloneExperiment(
             pb2.CloneExperimentRequest(
                 experiment_id=experiment_id,
                 new_experiment_name=new_experiment_name,
@@ -209,6 +216,7 @@ class ResearchClient:
             ),
             metadata=self._metadata,
         )
+        return response.experiment_id
 
     def terminate_experiment(self, experiment_id, gateway_id):
         pb2 = self._svc("experiment_service_pb2")
@@ -223,10 +231,11 @@ class ResearchClient:
 
     def create_project(self, gateway_id, project):
         pb2 = self._svc("project_service_pb2")
-        return self._project.CreateProject(
+        response = self._project.CreateProject(
             pb2.CreateProjectRequest(gateway_id=gateway_id, project=project),
             metadata=self._metadata,
         )
+        return response.project_id
 
     def update_project(self, project_id, project):
         pb2 = self._svc("project_service_pb2")
@@ -251,17 +260,19 @@ class ResearchClient:
 
     def get_user_projects(self, gateway_id, user_name, limit=-1, offset=0):
         pb2 = self._svc("project_service_pb2")
-        return self._project.GetUserProjects(
+        response = self._project.GetUserProjects(
             pb2.GetUserProjectsRequest(gateway_id=gateway_id, 
user_name=user_name, limit=limit, offset=offset),
             metadata=self._metadata,
         )
+        return list(response.projects)
 
     def search_projects(self, gateway_id, user_name, filters=None, limit=-1, 
offset=0):
         pb2 = self._svc("project_service_pb2")
-        return self._project.SearchProjects(
+        response = self._project.SearchProjects(
             pb2.SearchProjectsRequest(gateway_id=gateway_id, 
user_name=user_name, filters=filters or {}, limit=limit, offset=offset),
             metadata=self._metadata,
         )
+        return list(response.projects)
 
     # ================================================================
     # Application Catalog Service
@@ -269,10 +280,11 @@ class ResearchClient:
 
     def register_application_module(self, gateway_id, application_module):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.RegisterApplicationModule(
+        response = self._app_catalog.RegisterApplicationModule(
             pb2.RegisterApplicationModuleRequest(gateway_id=gateway_id, 
application_module=application_module),
             metadata=self._metadata,
         )
+        return response.app_module_id
 
     def get_application_module(self, app_module_id):
         pb2 = self._svc("application_catalog_service_pb2")
@@ -290,17 +302,19 @@ class ResearchClient:
 
     def get_all_app_modules(self, gateway_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetAllAppModules(
+        response = self._app_catalog.GetAllAppModules(
             pb2.GetAllAppModulesRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.application_modules)
 
     def get_accessible_app_modules(self, gateway_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetAccessibleAppModules(
+        response = self._app_catalog.GetAccessibleAppModules(
             pb2.GetAccessibleAppModulesRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.application_modules)
 
     def delete_application_module(self, app_module_id):
         pb2 = self._svc("application_catalog_service_pb2")
@@ -311,10 +325,11 @@ class ResearchClient:
 
     def register_application_deployment(self, gateway_id, 
application_deployment):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.RegisterApplicationDeployment(
+        response = self._app_catalog.RegisterApplicationDeployment(
             pb2.RegisterApplicationDeploymentRequest(gateway_id=gateway_id, 
application_deployment=application_deployment),
             metadata=self._metadata,
         )
+        return response.app_deployment_id
 
     def get_application_deployment(self, app_deployment_id):
         pb2 = self._svc("application_catalog_service_pb2")
@@ -339,42 +354,47 @@ class ResearchClient:
 
     def get_all_application_deployments(self, gateway_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetAllApplicationDeployments(
+        response = self._app_catalog.GetAllApplicationDeployments(
             pb2.GetAllApplicationDeploymentsRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.application_deployments)
 
     def get_accessible_application_deployments(self, gateway_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetAccessibleApplicationDeployments(
+        response = self._app_catalog.GetAccessibleApplicationDeployments(
             
pb2.GetAccessibleApplicationDeploymentsRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.application_deployments)
 
     def get_app_module_deployed_resources(self, app_module_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetAppModuleDeployedResources(
+        response = self._app_catalog.GetAppModuleDeployedResources(
             
pb2.GetAppModuleDeployedResourcesRequest(app_module_id=app_module_id),
             metadata=self._metadata,
         )
+        return list(response.compute_resource_ids)
 
     def 
get_application_deployments_for_app_module_and_group_resource_profile(self, 
app_module_id, group_resource_profile_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetDeploymentsForModuleAndProfile(
+        response = self._app_catalog.GetDeploymentsForModuleAndProfile(
             
pb2.GetDeploymentsForModuleAndProfileRequest(app_module_id=app_module_id, 
group_resource_profile_id=group_resource_profile_id),
             metadata=self._metadata,
         )
+        return list(response.application_deployments)
 
     def register_application_interface(self, gateway_id, 
application_interface):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.RegisterApplicationInterface(
+        response = self._app_catalog.RegisterApplicationInterface(
             pb2.RegisterApplicationInterfaceRequest(gateway_id=gateway_id, 
application_interface=application_interface),
             metadata=self._metadata,
         )
+        return response.app_interface_id
 
     def clone_application_interface(self, existing_app_interface_id, 
new_app_module_name, gateway_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.CloneApplicationInterface(
+        response = self._app_catalog.CloneApplicationInterface(
             pb2.CloneApplicationInterfaceRequest(
                 existing_app_interface_id=existing_app_interface_id,
                 new_app_module_name=new_app_module_name,
@@ -382,6 +402,7 @@ class ResearchClient:
             ),
             metadata=self._metadata,
         )
+        return response.app_interface_id
 
     def get_application_interface(self, app_interface_id):
         pb2 = self._svc("application_catalog_service_pb2")
@@ -406,38 +427,43 @@ class ResearchClient:
 
     def get_all_application_interface_names(self, gateway_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetAllApplicationInterfaceNames(
+        response = self._app_catalog.GetAllApplicationInterfaceNames(
             pb2.GetAllApplicationInterfaceNamesRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return dict(response.application_interface_names)
 
     def get_all_application_interfaces(self, gateway_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetAllApplicationInterfaces(
+        response = self._app_catalog.GetAllApplicationInterfaces(
             pb2.GetAllApplicationInterfacesRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.application_interfaces)
 
     def get_application_inputs(self, app_interface_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetApplicationInputs(
+        response = self._app_catalog.GetApplicationInputs(
             pb2.GetApplicationInputsRequest(app_interface_id=app_interface_id),
             metadata=self._metadata,
         )
+        return list(response.application_inputs)
 
     def get_application_outputs(self, app_interface_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetApplicationOutputs(
+        response = self._app_catalog.GetApplicationOutputs(
             
pb2.GetApplicationOutputsRequest(app_interface_id=app_interface_id),
             metadata=self._metadata,
         )
+        return list(response.application_outputs)
 
     def get_available_app_interface_compute_resources(self, app_interface_id):
         pb2 = self._svc("application_catalog_service_pb2")
-        return self._app_catalog.GetAvailableComputeResources(
+        response = self._app_catalog.GetAvailableComputeResources(
             
pb2.GetAvailableComputeResourcesRequest(app_interface_id=app_interface_id),
             metadata=self._metadata,
         )
+        return dict(response.compute_resource_names)
 
     # ================================================================
     # Parser Service
@@ -452,17 +478,19 @@ class ResearchClient:
 
     def save_parser(self, parser):
         pb2 = self._svc("parser_service_pb2")
-        return self._parser.SaveParser(
+        response = self._parser.SaveParser(
             pb2.SaveParserRequest(parser=parser),
             metadata=self._metadata,
         )
+        return response.parser_id
 
     def list_all_parsers(self, gateway_id):
         pb2 = self._svc("parser_service_pb2")
-        return self._parser.ListAllParsers(
+        response = self._parser.ListAllParsers(
             pb2.ListAllParsersRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.parsers)
 
     def remove_parser(self, parser_id, gateway_id):
         pb2 = self._svc("parser_service_pb2")
@@ -480,17 +508,19 @@ class ResearchClient:
 
     def get_parsing_templates_for_experiment(self, experiment_id, gateway_id):
         pb2 = self._svc("parser_service_pb2")
-        return self._parser.GetParsingTemplatesForExperiment(
+        response = self._parser.GetParsingTemplatesForExperiment(
             
pb2.GetParsingTemplatesForExperimentRequest(experiment_id=experiment_id, 
gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.parsing_templates)
 
     def save_parsing_template(self, parsing_template):
         pb2 = self._svc("parser_service_pb2")
-        return self._parser.SaveParsingTemplate(
+        response = self._parser.SaveParsingTemplate(
             pb2.SaveParsingTemplateRequest(parsing_template=parsing_template),
             metadata=self._metadata,
         )
+        return response.template_id
 
     def remove_parsing_template(self, template_id, gateway_id):
         pb2 = self._svc("parser_service_pb2")
@@ -501,10 +531,11 @@ class ResearchClient:
 
     def list_all_parsing_templates(self, gateway_id):
         pb2 = self._svc("parser_service_pb2")
-        return self._parser.ListAllParsingTemplates(
+        response = self._parser.ListAllParsingTemplates(
             pb2.ListAllParsingTemplatesRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.parsing_templates)
 
     # ================================================================
     # Data Product Service
@@ -512,10 +543,11 @@ class ResearchClient:
 
     def register_data_product(self, data_product):
         pb2 = self._svc("data_product_service_pb2")
-        return self._data_product.RegisterDataProduct(
+        response = self._data_product.RegisterDataProduct(
             pb2.RegisterDataProductRequest(data_product=data_product),
             metadata=self._metadata,
         )
+        return response.product_uri
 
     def get_data_product(self, data_product_uri):
         pb2 = self._svc("data_product_service_pb2")
@@ -526,10 +558,11 @@ class ResearchClient:
 
     def register_replica_location(self, replica_location):
         pb2 = self._svc("data_product_service_pb2")
-        return self._data_product.RegisterReplicaLocation(
+        response = self._data_product.RegisterReplicaLocation(
             
pb2.RegisterReplicaLocationRequest(replica_location=replica_location),
             metadata=self._metadata,
         )
+        return response.replica_id
 
     def get_parent_data_product(self, data_product_uri):
         pb2 = self._svc("data_product_service_pb2")
@@ -540,10 +573,11 @@ class ResearchClient:
 
     def get_child_data_products(self, data_product_uri):
         pb2 = self._svc("data_product_service_pb2")
-        return self._data_product.GetChildDataProducts(
+        response = self._data_product.GetChildDataProducts(
             pb2.GetChildDataProductsRequest(data_product_uri=data_product_uri),
             metadata=self._metadata,
         )
+        return list(response.data_products)
 
     def update_data_product(self, product_uri, data_product):
         pb2 = self._svc("data_product_service_pb2")
@@ -586,10 +620,11 @@ class ResearchClient:
 
     def create_notification(self, notification):
         pb2 = self._svc("notification_service_pb2")
-        return self._notification.CreateNotification(
+        response = self._notification.CreateNotification(
             pb2.CreateNotificationRequest(notification=notification),
             metadata=self._metadata,
         )
+        return response.notification_id
 
     def update_notification(self, notification):
         pb2 = self._svc("notification_service_pb2")
@@ -614,10 +649,11 @@ class ResearchClient:
 
     def get_all_notifications(self, gateway_id):
         pb2 = self._svc("notification_service_pb2")
-        return self._notification.GetAllNotifications(
+        response = self._notification.GetAllNotifications(
             pb2.GetAllNotificationsRequest(gateway_id=gateway_id),
             metadata=self._metadata,
         )
+        return list(response.notifications)
 
     # ================================================================
     # Experiment Management Service
diff --git a/airavata-python-sdk/airavata_sdk/facade/sharing.py 
b/airavata-python-sdk/airavata_sdk/facade/sharing.py
index 1ee527b338..ea5bb31a56 100644
--- a/airavata-python-sdk/airavata_sdk/facade/sharing.py
+++ b/airavata-python-sdk/airavata_sdk/facade/sharing.py
@@ -68,38 +68,43 @@ class SharingClient:
 
     def get_all_accessible_users(self, resource_id, permission_type):
         pb2 = self._pb2()
-        return self._sharing.GetAllAccessibleUsers(
+        response = self._sharing.GetAllAccessibleUsers(
             pb2.GetAllAccessibleUsersRequest(resource_id=resource_id, 
permission_type=permission_type),
             metadata=self._metadata,
         )
+        return list(response.user_ids)
 
     def get_all_directly_accessible_users(self, resource_id, permission_type):
         pb2 = self._pb2()
-        return self._sharing.GetAllDirectlyAccessibleUsers(
+        response = self._sharing.GetAllDirectlyAccessibleUsers(
             pb2.GetAllDirectlyAccessibleUsersRequest(resource_id=resource_id, 
permission_type=permission_type),
             metadata=self._metadata,
         )
+        return list(response.user_ids)
 
     def get_all_accessible_groups(self, resource_id, permission_type):
         pb2 = self._pb2()
-        return self._sharing.GetAllAccessibleGroups(
+        response = self._sharing.GetAllAccessibleGroups(
             pb2.GetAllAccessibleGroupsRequest(resource_id=resource_id, 
permission_type=permission_type),
             metadata=self._metadata,
         )
+        return list(response.group_ids)
 
     def get_all_directly_accessible_groups(self, resource_id, permission_type):
         pb2 = self._pb2()
-        return self._sharing.GetAllDirectlyAccessibleGroups(
+        response = self._sharing.GetAllDirectlyAccessibleGroups(
             pb2.GetAllDirectlyAccessibleGroupsRequest(resource_id=resource_id, 
permission_type=permission_type),
             metadata=self._metadata,
         )
+        return list(response.group_ids)
 
     def user_has_access(self, resource_id, user_id, permission_type):
         pb2 = self._pb2()
-        return self._sharing.UserHasAccess(
+        response = self._sharing.UserHasAccess(
             pb2.UserHasAccessRequest(resource_id=resource_id, user_id=user_id, 
permission_type=permission_type),
             metadata=self._metadata,
         )
+        return response.has_access
 
     def revoke_from_users(self, resource_id, user_permissions):
         pb2 = self._pb2()
diff --git a/airavata-python-sdk/airavata_sdk/facade/storage.py 
b/airavata-python-sdk/airavata_sdk/facade/storage.py
index bec3fda26e..b6a1ca80e7 100644
--- a/airavata-python-sdk/airavata_sdk/facade/storage.py
+++ b/airavata-python-sdk/airavata_sdk/facade/storage.py
@@ -22,10 +22,11 @@ class StorageClient:
 
     def register_storage_resource(self, storage_resource):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.RegisterStorageResource(
+        response = self._resource.RegisterStorageResource(
             
pb2.RegisterStorageResourceRequest(storage_resource=storage_resource),
             metadata=self._metadata,
         )
+        return response.storage_resource_id
 
     def get_storage_resource(self, storage_resource_id):
         pb2 = self._svc("resource_service_pb2")
@@ -50,10 +51,11 @@ class StorageClient:
 
     def get_all_storage_resource_names(self):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.GetAllStorageResourceNames(
+        response = self._resource.GetAllStorageResourceNames(
             pb2.GetAllStorageResourceNamesRequest(),
             metadata=self._metadata,
         )
+        return dict(response.storage_resource_names)
 
     # ================================================================
     # Data Movement
@@ -61,10 +63,11 @@ class StorageClient:
 
     def add_local_data_movement(self, compute_resource_id, priority, dm_type, 
local_data_movement):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.AddLocalDataMovement(
+        response = self._resource.AddLocalDataMovement(
             
pb2.AddLocalDataMovementRequest(compute_resource_id=compute_resource_id, 
priority=priority, dm_type=dm_type, local_data_movement=local_data_movement),
             metadata=self._metadata,
         )
+        return response.data_movement_id
 
     def update_local_data_movement(self, data_movement_id, 
local_data_movement):
         pb2 = self._svc("resource_service_pb2")
@@ -82,10 +85,11 @@ class StorageClient:
 
     def add_scp_data_movement(self, compute_resource_id, priority, dm_type, 
scp_data_movement):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.AddSCPDataMovement(
+        response = self._resource.AddSCPDataMovement(
             
pb2.AddSCPDataMovementRequest(compute_resource_id=compute_resource_id, 
priority=priority, dm_type=dm_type, scp_data_movement=scp_data_movement),
             metadata=self._metadata,
         )
+        return response.data_movement_id
 
     def update_scp_data_movement(self, data_movement_id, scp_data_movement):
         pb2 = self._svc("resource_service_pb2")
@@ -103,10 +107,11 @@ class StorageClient:
 
     def add_grid_ftp_data_movement(self, compute_resource_id, priority, 
dm_type, gridftp_data_movement):
         pb2 = self._svc("resource_service_pb2")
-        return self._resource.AddGridFTPDataMovement(
+        response = self._resource.AddGridFTPDataMovement(
             
pb2.AddGridFTPDataMovementRequest(compute_resource_id=compute_resource_id, 
priority=priority, dm_type=dm_type, 
gridftp_data_movement=gridftp_data_movement),
             metadata=self._metadata,
         )
+        return response.data_movement_id
 
     def update_grid_ftp_data_movement(self, data_movement_id, 
gridftp_data_movement):
         pb2 = self._svc("resource_service_pb2")
diff --git a/airavata-server/src/main/resources/application.properties 
b/airavata-server/src/main/resources/application.properties
index a008a1839a..371bda938f 100644
--- a/airavata-server/src/main/resources/application.properties
+++ b/airavata-server/src/main/resources/application.properties
@@ -28,7 +28,7 @@ spring.datasource.hikari.leak-detection-threshold=20000
 validationQuery=SELECT 1
 
 # --- JPA ---
-spring.jpa.hibernate.ddl-auto=none
+spring.jpa.hibernate.ddl-auto=update
 
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
 spring.jpa.open-in-view=false
 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
@@ -62,7 +62,7 @@ credential.store.keystore.alias=airavata
 # --- Gateway ---
 default.registry.gateway=default
 default.registry.oauth.client.id=pga
-default.registry.oauth.client.secret=upCMVu2RZcAXUqpr9V7phAbz6hhF9cbl
+default.registry.oauth.client.secret=m36BXQIxX3j3VILadeHMK5IvbOeRlCCc
 default.registry.user=default-admin
 default.registry.password=ade4#21242ftfd
 super.tenant.gatewayId=default
diff --git 
a/airavata-server/src/main/resources/db/migration/airavata/V1__Baseline_schema.sql
 
b/airavata-server/src/main/resources/db/migration/airavata/V1__Baseline_schema.sql
index f6d9c20229..6efa3ee192 100644
--- 
a/airavata-server/src/main/resources/db/migration/airavata/V1__Baseline_schema.sql
+++ 
b/airavata-server/src/main/resources/db/migration/airavata/V1__Baseline_schema.sql
@@ -48,6 +48,7 @@ CREATE TABLE IF NOT EXISTS GATEWAY (
     OAUTH_CLIENT_SECRET                 VARCHAR(255),
     REQUEST_CREATION_TIME               TIMESTAMP NULL,
     REQUESTER_USERNAME                  VARCHAR(255),
+    AIRAVATA_INTERNAL_GATEWAY_ID        VARCHAR(255),
     PRIMARY KEY (GATEWAY_ID)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
diff --git a/compose.yml b/compose.yml
index 199d309378..9123c0ab33 100644
--- a/compose.yml
+++ b/compose.yml
@@ -4,6 +4,7 @@ services:
     image: mariadb:11.8
     container_name: airavata-db
     restart: unless-stopped
+    command: --lower-case-table-names=1
     environment:
       MYSQL_ROOT_PASSWORD: rootpass
       MYSQL_DATABASE: airavata
@@ -90,6 +91,7 @@ services:
       KEYCLOAK_ADMIN_PASSWORD: admin
     volumes:
       - 
./conf/keycloak/realm-default.json:/opt/keycloak/data/import/realm-default.json:ro
+      - ./conf/keycloak/themes/airavata:/opt/keycloak/themes/airavata:ro
     command:
       - start-dev
       - --import-realm
@@ -100,6 +102,7 @@ services:
       - --health-enabled=true
       - --metrics-enabled=true
       - --log-level=INFO
+      - --spi-theme-default=airavata
     ports:
       - "18080:18080"
     healthcheck:
@@ -109,6 +112,23 @@ services:
       retries: 10
       start_period: 60s
 
+  sftp:
+    image: atmoz/sftp:latest
+    container_name: airavata-sftp
+    restart: unless-stopped
+    volumes:
+      - ./conf/sftp/id_rsa.pub:/home/airavata/.ssh/keys/id_rsa.pub:ro
+      - sftp_data:/home/airavata/storage
+    ports:
+      - "2222:22"
+    command: "airavata::1000"
+    healthcheck:
+      test: ["CMD-SHELL", "cat /proc/1/cmdline | tr '\\0' ' ' | grep -q sshd 
|| exit 1"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+      start_period: 10s
+
   # Dev tools (start with: docker compose --profile tools up -d)
 
   adminer:
@@ -128,3 +148,4 @@ volumes:
   zk_data:
   zk_logs:
   kafka_data:
+  sftp_data:
diff --git a/conf/keycloak/realm-default.json b/conf/keycloak/realm-default.json
index d8bac079b0..3eb295fcb3 100644
--- a/conf/keycloak/realm-default.json
+++ b/conf/keycloak/realm-default.json
@@ -1395,8 +1395,8 @@
     {
       "id": "5e2398e0-3498-4da3-9262-4f2dcc7448fa",
       "clientId": "pga",
-      "name": "Cybeshuttle Client",
-      "description": "Client For Cybershuttle Services",
+      "name": "Airavata Portal",
+      "description": "Client for Airavata Portal",
       "rootUrl": "",
       "adminUrl": "",
       "baseUrl": "",
@@ -1406,8 +1406,7 @@
       "clientAuthenticatorType": "client-secret",
       "secret": "m36BXQIxX3j3VILadeHMK5IvbOeRlCCc",
       "redirectUris": [
-        "http://airavata.localhost:8008/callback*";,
-        "http://airavata.localhost:8009/auth/callback*";
+        "http://localhost:8000/*";
       ],
       "webOrigins": [
         "+"
@@ -2297,7 +2296,7 @@
     "strictTransportSecurity": ""
   },
   "smtpServer": {},
-  "loginTheme": "custom-theme",
+  "loginTheme": "airavata",
   "accountTheme": "",
   "adminTheme": "",
   "emailTheme": "",
@@ -2578,14 +2577,12 @@
           "authenticatorFlow": false,
           "requirement": "ALTERNATIVE",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorFlow": true,
           "requirement": "ALTERNATIVE",
           "priority": 20,
-          "autheticatorFlow": true,
           "flowAlias": "Verify Existing Account by Re-authentication",
           "userSetupAllowed": false
         }
@@ -2604,7 +2601,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -2612,7 +2608,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 20,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         }
       ]
@@ -2630,7 +2625,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -2638,7 +2632,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 20,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         }
       ]
@@ -2656,7 +2649,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -2664,7 +2656,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 20,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         }
       ]
@@ -2682,14 +2673,12 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorFlow": true,
           "requirement": "REQUIRED",
           "priority": 20,
-          "autheticatorFlow": true,
           "flowAlias": "Account verification options",
           "userSetupAllowed": false
         }
@@ -2708,7 +2697,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -2716,7 +2704,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 20,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         }
       ]
@@ -2735,14 +2722,12 @@
           "authenticatorFlow": false,
           "requirement": "ALTERNATIVE",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorFlow": true,
           "requirement": "ALTERNATIVE",
           "priority": 20,
-          "autheticatorFlow": true,
           "flowAlias": "Handle Existing Account",
           "userSetupAllowed": false
         }
@@ -2761,14 +2746,12 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorFlow": true,
           "requirement": "CONDITIONAL",
           "priority": 20,
-          "autheticatorFlow": true,
           "flowAlias": "First broker login - Conditional OTP",
           "userSetupAllowed": false
         }
@@ -2787,7 +2770,6 @@
           "authenticatorFlow": false,
           "requirement": "ALTERNATIVE",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -2795,23 +2777,20 @@
           "authenticatorFlow": false,
           "requirement": "DISABLED",
           "priority": 20,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorConfig": "oidc",
           "authenticator": "identity-provider-redirector",
           "authenticatorFlow": false,
-          "requirement": "ALTERNATIVE",
+          "requirement": "DISABLED",
           "priority": 25,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorFlow": true,
           "requirement": "ALTERNATIVE",
           "priority": 30,
-          "autheticatorFlow": true,
           "flowAlias": "forms",
           "userSetupAllowed": false
         }
@@ -2830,7 +2809,6 @@
           "authenticatorFlow": false,
           "requirement": "ALTERNATIVE",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -2838,7 +2816,6 @@
           "authenticatorFlow": false,
           "requirement": "ALTERNATIVE",
           "priority": 20,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -2846,7 +2823,6 @@
           "authenticatorFlow": false,
           "requirement": "ALTERNATIVE",
           "priority": 30,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -2854,7 +2830,6 @@
           "authenticatorFlow": false,
           "requirement": "ALTERNATIVE",
           "priority": 40,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         }
       ]
@@ -2872,7 +2847,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -2880,14 +2854,12 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 20,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorFlow": true,
           "requirement": "CONDITIONAL",
           "priority": 30,
-          "autheticatorFlow": true,
           "flowAlias": "Direct Grant - Conditional OTP",
           "userSetupAllowed": false
         }
@@ -2906,7 +2878,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         }
       ]
@@ -2925,14 +2896,12 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorFlow": true,
           "requirement": "REQUIRED",
           "priority": 20,
-          "autheticatorFlow": true,
           "flowAlias": "User creation or linking",
           "userSetupAllowed": false
         }
@@ -2951,14 +2920,12 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorFlow": true,
           "requirement": "CONDITIONAL",
           "priority": 20,
-          "autheticatorFlow": true,
           "flowAlias": "Browser - Conditional OTP",
           "userSetupAllowed": false
         }
@@ -2977,7 +2944,6 @@
           "authenticatorFlow": true,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": true,
           "flowAlias": "registration form",
           "userSetupAllowed": false
         }
@@ -2996,7 +2962,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 20,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -3004,7 +2969,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 50,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -3012,7 +2976,6 @@
           "authenticatorFlow": false,
           "requirement": "DISABLED",
           "priority": 60,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -3020,7 +2983,6 @@
           "authenticatorFlow": false,
           "requirement": "DISABLED",
           "priority": 70,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         }
       ]
@@ -3038,7 +3000,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -3046,7 +3007,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 20,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
@@ -3054,14 +3014,12 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 30,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         },
         {
           "authenticatorFlow": true,
           "requirement": "CONDITIONAL",
           "priority": 40,
-          "autheticatorFlow": true,
           "flowAlias": "Reset - Conditional OTP",
           "userSetupAllowed": false
         }
@@ -3080,7 +3038,6 @@
           "authenticatorFlow": false,
           "requirement": "REQUIRED",
           "priority": 10,
-          "autheticatorFlow": false,
           "userSetupAllowed": false
         }
       ]
diff --git a/conf/keycloak/themes/airavata/login/resources/css/custom.css 
b/conf/keycloak/themes/airavata/login/resources/css/custom.css
new file mode 100644
index 0000000000..7acffbf572
--- /dev/null
+++ b/conf/keycloak/themes/airavata/login/resources/css/custom.css
@@ -0,0 +1,297 @@
+/* Airavata Portal Keycloak Theme — pixel-matched to portal landing page */
+
+/* ── NUCLEAR: Force all card elements to 480px width ── */
+.pf-v5-c-login__container,
+.pf-v5-c-login__container > *,
+.pf-v5-c-login__header,
+.pf-v5-c-login__main,
+.pf-v5-c-login__main-header,
+.pf-v5-c-login__main-footer {
+  width: 480px !important;
+  min-width: 480px !important;
+  max-width: 480px !important;
+  box-sizing: border-box !important;
+}
+
+/* ── Background ── */
+html, body, .login-pf, .pf-v5-c-login {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
+  min-height: 100vh !important;
+}
+
+/* ── Center everything ── */
+.pf-v5-c-login {
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+  padding: 2rem 0 !important;
+}
+
+/* Kill any grid/flex that shifts the container off-center */
+.pf-v5-c-login > * {
+  margin: 0 auto !important;
+}
+
+/* Absolute center the container */
+.pf-v5-c-login__container {
+  position: absolute !important;
+  left: 50% !important;
+  top: 50% !important;
+  transform: translate(-50%, -50%) !important;
+}
+
+/* ── Container: single column, card width ── */
+.pf-v5-c-login__container {
+  display: flex !important;
+  flex-direction: column !important;
+  align-items: stretch !important;
+  width: 480px !important;
+  min-width: 480px !important;
+  max-width: 480px !important;
+  grid-template-columns: unset !important;
+  grid-template-rows: unset !important;
+  gap: 0 !important;
+}
+
+/* ── Realm header: INSIDE the card as top section ── */
+.pf-v5-c-login__header {
+  background: rgba(255, 255, 255, 0.95) !important;
+  border-radius: 16px 16px 0 0 !important;
+  text-align: center !important;
+  padding: 1.75rem 2rem 0 !important;
+  margin: 0 !important;
+  grid-area: unset !important;
+  box-shadow: none !important;
+  border-bottom: none !important;
+  width: 100% !important;
+  align-self: stretch !important;
+}
+
+#kc-header-wrapper {
+  color: #1a1a2e !important;
+  font-weight: 700 !important;
+  letter-spacing: 0.02em !important;
+  text-transform: none !important;
+  font-size: 1.75rem !important;
+  margin-bottom: 0 !important;
+  padding-bottom: 0 !important;
+}
+
+/* Logo above realm name — same as portal landing page */
+#kc-header-wrapper::before {
+  content: '' !important;
+  display: block !important;
+  width: 80px !important;
+  height: 80px !important;
+  margin: 0 auto 0.5rem !important;
+  background: url('../img/logo.png') center/contain no-repeat !important;
+}
+
+/* ── Card body ── */
+.pf-v5-c-login__main {
+  background: rgba(255, 255, 255, 0.95) !important;
+  backdrop-filter: blur(10px) !important;
+  border-radius: 0 0 16px 16px !important;
+  box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15) !important;
+  border-top: none !important;
+  overflow: hidden !important;
+  width: 100% !important;
+  align-self: stretch !important;
+  grid-area: unset !important;
+}
+
+/* ── Card header (title) ── */
+.pf-v5-c-login__main-header {
+  padding: 0.25rem 2rem 0 !important;
+  background: transparent !important;
+  text-align: center !important;
+}
+
+.pf-v5-c-login__main-header .pf-v5-c-title,
+#kc-page-title {
+  color: #666 !important;
+  font-weight: 400 !important;
+  font-size: 1rem !important;
+  text-align: center !important;
+  margin-bottom: 1rem !important;
+}
+
+/* ── Card body (form) ── */
+.pf-v5-c-login__main-body {
+  padding: 0 2rem 1rem !important;
+  background: transparent !important;
+}
+
+/* Tight form spacing */
+.pf-v5-c-login__main-body .pf-v5-c-form {
+  gap: 0 !important;
+}
+.pf-v5-c-login__main-body .pf-v5-c-form__group {
+  margin-top: 0 !important;
+  margin-bottom: 0.5rem !important;
+}
+.pf-v5-c-login__main-body .pf-v5-c-form__group:last-child {
+  margin-top: 0 !important;
+  margin-bottom: 0 !important;
+}
+
+/* ── Labels ── */
+.pf-v5-c-form__label-text, label {
+  color: #333 !important;
+  font-weight: 500 !important;
+  font-size: 0.9rem !important;
+}
+
+/* ── Inputs ── */
+.pf-v5-c-form-control,
+.pf-v5-c-input-group,
+.pf-v5-c-text-input-group {
+  border: 1px solid #dee2e6 !important;
+  border-radius: 8px !important;
+  background: #fff !important;
+  overflow: hidden !important;
+}
+
+.pf-v5-c-form-control:focus-within,
+.pf-v5-c-text-input-group:focus-within,
+.pf-v5-c-input-group:focus-within {
+  border-color: #667eea !important;
+  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2) !important;
+}
+
+input[type="text"], input[type="password"], input[type="email"] {
+  color: #333 !important;
+  padding: 0.625rem 0.75rem !important;
+}
+
+/* ── Password toggle ── */
+.pf-v5-c-button.pf-m-control {
+  background: #fff !important;
+  color: #666 !important;
+  border: none !important;
+  border-left: 1px solid #dee2e6 !important;
+  border-radius: 0 !important;
+  padding: 0.5rem 0.75rem !important;
+}
+
+.pf-v5-c-button.pf-m-control:hover {
+  background: #f0f0f0 !important;
+  color: #333 !important;
+}
+
+/* ── Primary button ── */
+.pf-v5-c-button.pf-m-primary,
+#kc-login {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
+  border: none !important;
+  border-radius: 8px !important;
+  padding: 0.875rem 2.5rem !important;
+  font-weight: 600 !important;
+  font-size: 1rem !important;
+  box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
+  color: #fff !important;
+  transition: transform 0.15s ease, box-shadow 0.15s ease !important;
+  width: 100% !important;
+  display: block !important;
+  text-align: center !important;
+  margin-top: 0.5rem !important;
+}
+
+.pf-v5-c-button.pf-m-primary:hover,
+#kc-login:hover {
+  transform: translateY(-2px) !important;
+  box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important;
+  color: #fff !important;
+}
+
+/* ── Footer (social/IdP) ── */
+.pf-v5-c-login__main-footer {
+  padding: 0.25rem 2rem 1rem !important;
+  background: transparent !important;
+  grid-area: unset !important;
+}
+
+/* Make footer content same width as form body */
+.pf-v5-c-login__main-footer-links-item {
+  padding: 0 !important;
+}
+
+.pf-v5-c-login__main-footer-band {
+  border-top: none !important;
+  padding-top: 0 !important;
+  margin-top: 0 !important;
+  background: transparent !important;
+}
+
+.pf-v5-c-login__main-footer-band-item:first-child {
+  color: #999 !important;
+  font-size: 0.85rem !important;
+  text-align: center !important;
+  margin-bottom: 0 !important;
+  padding: 0 !important;
+  line-height: 1.2 !important;
+}
+
+/* IdP buttons — muted outlined style */
+.pf-v5-c-login__main-footer-links {
+  list-style: none !important;
+  padding: 0 !important;
+  margin: 0 !important;
+  margin-top: 0 !important;
+  display: flex !important;
+  flex-direction: column !important;
+  gap: 0.5rem !important;
+}
+
+.pf-v5-c-login__main-footer-links-item {
+  width: 100% !important;
+}
+
+.pf-v5-c-login__main-footer-links-item-link {
+  border-radius: 8px !important;
+  border: 2px solid #667eea !important;
+  background: transparent !important;
+  color: #667eea !important;
+  padding: 0.625rem 1rem !important;
+  width: 100% !important;
+  display: inline-flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+  gap: 0.5rem !important;
+  font-weight: 600 !important;
+  font-size: 0.95rem !important;
+  line-height: 1.4 !important;
+  transition: all 0.15s ease !important;
+  text-decoration: none !important;
+  box-sizing: border-box !important;
+}
+
+.pf-v5-c-login__main-footer-links-item-link img,
+.pf-v5-c-login__main-footer-links-item-link svg {
+  width: 20px !important;
+  height: 20px !important;
+  vertical-align: middle !important;
+  flex-shrink: 0 !important;
+  filter: brightness(0) saturate(100%) invert(40%) sepia(85%) saturate(800%) 
hue-rotate(210deg) !important;
+}
+
+.pf-v5-c-login__main-footer-links-item-link span {
+  vertical-align: middle !important;
+}
+
+.pf-v5-c-login__main-footer-links-item-link:hover {
+  background: rgba(102, 126, 234, 0.08) !important;
+  border-color: #764ba2 !important;
+  color: #764ba2 !important;
+  text-decoration: none !important;
+}
+
+/* ── Links ── */
+.pf-v5-c-login__main-body a { color: #667eea !important; }
+.pf-v5-c-login__main-body a:hover { color: #764ba2 !important; }
+
+/* ── Alerts ── */
+.pf-v5-c-alert { border-radius: 8px !important; }
+
+/* ── Hide Keycloak footer ── */
+.pf-v5-c-login__footer { display: none !important; }
diff --git a/conf/keycloak/themes/airavata/login/resources/img/logo.png 
b/conf/keycloak/themes/airavata/login/resources/img/logo.png
new file mode 100644
index 0000000000..65b39992c6
Binary files /dev/null and 
b/conf/keycloak/themes/airavata/login/resources/img/logo.png differ
diff --git a/conf/keycloak/themes/airavata/login/theme.properties 
b/conf/keycloak/themes/airavata/login/theme.properties
new file mode 100644
index 0000000000..7349d78df7
--- /dev/null
+++ b/conf/keycloak/themes/airavata/login/theme.properties
@@ -0,0 +1,4 @@
+parent=keycloak.v2
+import=common/keycloak
+
+styles=css/login.css css/custom.css
diff --git a/pom.xml b/pom.xml
index 8a57cd9ab2..85626ced23 100644
--- a/pom.xml
+++ b/pom.xml
@@ -493,6 +493,13 @@ under the License.
                 <configuration>
                     <release>17</release>
                     <fork>true</fork>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.mapstruct</groupId>
+                            <artifactId>mapstruct-processor</artifactId>
+                            <version>1.6.3</version>
+                        </path>
+                    </annotationProcessorPaths>
                 </configuration>
             </plugin>
             <plugin>

Reply via email to